我在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"));
答案 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(".");
}