28 votes

Pourquoi l'appel d'une expression lambda Python à partir de C# n'est-il pas sûr pour les threads ?

Je définis une expression lambda (pure) sans effet de bord dans IronPython et je l'affecte à un délégué C#. Lorsque j'invoque le délégué simultanément à partir de plusieurs threads, j'obtiens des exceptions de type Exception de violation d'accès , NullReferenceException y FatalEngineExecutionError (erreur fatale d'exécution du moteur) .

L'apparition de l'erreur n'est pas déterministe et il faut souvent plusieurs millions d'itérations pour la provoquer, ce qui me fait dire qu'il s'agit d'une "race condition". Comment puis-je l'éviter ?

Les exceptions ne sont levées que lors de l'exécution du processus avec x64 (x86 ne plante pas) et en dehors du débogueur. Le système de test est un Core I7 (8 threads) sous Windows 7, .NET Framework 4.0 et IronPython 2.7.1.

Voici le code minimal qui produit l'erreur :

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(".");   
}

Message d'erreur :

Une erreur fatale (FatalExecutionEngineError) a été détectée.

Message : Le runtime a rencontré une erreur fatale. L'adresse de l'erreur est 0xf807829e, sur le thread 0x3da0. Le code d'erreur est 0xc0000005. Cette erreur peut être un bogue dans le CLR ou dans les parties non sûres ou non vérifiables du code utilisateur. Parmi les sources courantes de ce bogue figurent les erreurs de marshaling de l'utilisateur pour COM-interop ou PInvoke, qui peuvent corrompre la pile.

Mise à jour : Même si le moteur est déclaré comme étant local aux threads, il se plante au bout d'un certain temps :

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

4voto

Polity Points 7316

Ceci est probablement dû à une condition de course dans le ScriptEngine. Notez que ScriptEngine.Execute renvoie une référence à une PythonFunction plutôt qu'à un Func (c'est grâce au comportement dynamique de C# que vous pouvez traiter le résultat comme un Func. Je ne suis pas un expert d'IronPython, mais en regardant la source de PythonFunction, il n'y a aucune indication qu'elle soit threadsafe.

1voto

Chiune Sugihara Points 68

Avez-vous essayé de remplacer le type de données 64 bits (double) par un type de données 32 bits (float) ? Je sais que "Une simple lecture ou écriture sur un champ de 32 bits ou moins est toujours atomique. alors qu'un champ 64 bits peut poser des problèmes en fonction du processeur et du code. Cela semble un peu long, mais cela vaut la peine d'essayer.

0voto

leppie Points 67289

L'exécution d'un code similaire (voir ci-dessous) sur IronScheme ne pose aucun problème de ce type.

La seule chose à laquelle je peux penser, c'est que engine.Execute("lambda a,b : a+b") crée une fonction interprétée au lieu d'une fonction compilée. Combinez cela avec un interpréteur qui n'est peut-être pas sûr pour les threads, et 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(".");
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X