为什么从C#调用Python lambda表达式不是线程安全的?

时间:2011-11-26 11:13:03

标签: c# lambda thread-safety ironpython

我在IronPython中定义了一个无副作用的(纯)lambda表达式,并将其分配给C#委托。当从多个线程同时调用委托时,我得到类型为 AccessViolationException NullReferenceException FatalEngineExecutionError 的异常。

错误的发生是非确定性的,并且大多需要几百万次迭代来激发它,这对我说“竞争条件”。我怎么能避免它?

仅在使用x64(x86不崩溃)且在调试器外部运行进程时才会引发异常。测试系统是Windows 7,.NET Framework 4.0和IronPython 2.7.1上的Core I7(8个线程)。

以下是产生错误的最小代码:

var engine = Python.CreateEngine();

double a = 1.0;
double b = 2.0;

while (true)
{
    Func<double, double, double> calculate = engine.Execute("lambda a,b : a+b");

    System.Threading.Tasks.Parallel.For(0, 1000, _ =>
    {
         for (int i = 0; i < 1000; i++) { calculate(a,b); }
    });

    Console.Write(".");   
}

错误讯息:

  

检测到FatalExecutionEngineError

     

消息:运行时遇到了致命错误。错误的地址是0xf807829e,位于线程0x3da0上。错误代码是0xc0000005。此错误可能是CLR中的错误,也可能是用户代码的不安全或不可验证部分中的错误。此错误的常见来源包括COM-interop或PInvoke的用户封送错误,这可能会破坏堆栈。

更新:即使引擎被声明为线程本地,它也会在一段时间后崩溃:

var calculate = new ThreadLocal<Func<double, double, double>>(() => Python.CreateEngine().Execute("lambda a,b : a+b"));

3 个答案:

答案 0 :(得分:4)

这可能是由于ScriptEngine中的竞争条件造成的。请注意,ScriptEngine.Execute返回对PythonFunction而不是Func的引用(这是由于C#的动态行为,您可以将结果视为Func。我不是IronPython的专家,但是查看PythonFunction的来源,有没有任何迹象表明它是线程安全的。

答案 1 :(得分:1)

您是否尝试用32位数据类型(浮点数)替换64位数据类型(double)?我知道"A simple read or write on a field of 32 bits or less is always atomic"而64位字段可能会有问题,具体取决于处理器和代码。这似乎是一个长镜头,但值得一试。

答案 2 :(得分:0)

在IronScheme上运行类似的代码(见下文),没有出现这样的问题。

我唯一能想到的是engine.Execute("lambda a,b : a+b")创建一个解释函数而不是编译函数。将它与可能线程不安全的解释器和kaboom结合起来。

double a = 1.0;
double b = 2.0;

while (true)
{
  var calculate = "(lambda (a b) (+ a b))".Eval<Callable>();

  System.Threading.Tasks.Parallel.For(0, 1000, _ =>
  {
    for (int i = 0; i < 1000; i++) { calculate.Call(a, b); }
  });

  Console.Write(".");
}
相关问题