47 votes

Pourquoi avons-nous besoin de Thread.MemoryBarrier() ?

Dans "C# 4 in a Nutshell", l'auteur montre que cette classe permet d'écrire 0 parfois sans MemoryBarrier mais je ne peux pas le reproduire sur mon Core2Duo :

public class Foo
{
    int _answer;
    bool _complete;
    public void A()
    {
        _answer = 123;
        //Thread.MemoryBarrier();    // Barrier 1
        _complete = true;
        //Thread.MemoryBarrier();    // Barrier 2
    }
    public void B()
    {
        //Thread.MemoryBarrier();    // Barrier 3
        if (_complete)
        {
            //Thread.MemoryBarrier();       // Barrier 4
            Console.WriteLine(_answer);
        }
    }
}

private static void ThreadInverteOrdemComandos()
{
    Foo obj = new Foo();

    Task.Factory.StartNew(obj.A);
    Task.Factory.StartNew(obj.B);

    Thread.Sleep(10);
}

Ce besoin me semble fou. Comment puis-je reconnaître tous les cas possibles où cela peut se produire ? Je pense que si le processeur change l'ordre des opérations, il doit garantir que le comportement ne change pas.

Prenez-vous la peine d'utiliser des barrières ?

66voto

Brian Gideon Points 26683

Vous allez avoir beaucoup de mal à reproduire ce bug. En fait, j'irais même jusqu'à dire que vous ne pourrez jamais le reproduire en utilisant le .NET Framework. La raison en est que l'implémentation de Microsoft utilise un modèle de mémoire fort pour les écritures. Cela signifie que les écritures sont traitées comme si elles étaient volatiles. Une écriture volatile a une sémantique de verrouillage et de libération, ce qui signifie que toutes les écritures précédentes doivent être validées avant l'écriture actuelle.

Toutefois, la spécification de l'ECMA présente un modèle de mémoire plus faible. Il est donc théoriquement possible que Mono ou même une future version du .NET Framework commence à présenter ce comportement bogué.

Ce que je veux dire, c'est qu'il est très peu probable que la suppression des obstacles n° 1 et n° 2 ait un quelconque impact sur le comportement du programme. Bien sûr, ce n'est pas une garantie, mais une observation basée uniquement sur l'implémentation actuelle du CLR.

La suppression des obstacles n° 3 et n° 4 aura certainement un impact. C'est en fait assez facile à reproduire. Enfin, pas cet exemple en soi, mais le code suivant est l'une des démonstrations les plus connues. Il doit être compilé en utilisant la version Release et exécuté en dehors du débogueur. Le problème est que le programme ne se termine pas. Vous pouvez corriger ce bogue en plaçant un appel à Thread.MemoryBarrier à l'intérieur de la while ou en marquant stop como volatile .

class Program
{
    static bool stop = false;

    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");
        t.Join();
    }
}

La raison pour laquelle certains bogues de threading sont difficiles à reproduire est que la même tactique que vous utilisez pour simuler l'entrelacement des fils peut en fait corriger le bogue. Thread.Sleep est l'exemple le plus notable car il génère des barrières de mémoire. Vous pouvez le vérifier en plaçant un appel à l'intérieur de la fonction while et observer que le bug disparaît.

Vous pouvez voir ma réponse aquí pour une autre analyse de l'exemple du livre que vous avez cité.

10voto

Hans Passant Points 475940

Les chances sont muy Il est bon que la première tâche soit terminée au moment où la deuxième commence à s'exécuter. Vous ne pouvez observer ce comportement que si les deux threads exécutent ce code simultanément et qu'il n'y a pas d'opérations intermédiaires de synchronisation du cache. Il y en a une dans votre code, la méthode StartNew() prend un verrou dans le gestionnaire de pool de threads quelque part.

Faire en sorte que deux threads exécutent ce code simultanément, c'est muy dur. Ce code s'exécute en quelques nanosecondes. Il faudrait essayer des milliards de fois et introduire des délais variables pour avoir une chance. Cela n'a pas beaucoup d'intérêt, bien sûr, le vrai problème est que cela se produit de façon aléatoire lorsque vous Ne le fais pas. s'y attendre.

Restez loin de cela, utilisez l'instruction lock pour écrire du code multithread sain.

2voto

Steven Sudit Points 13793

Si vous utilisez volatile y lock la barrière de la mémoire est intégrée. Mais, oui, vous en avez besoin autrement. Cela dit, je pense que vous en avez besoin de moitié moins que ce que montre votre exemple.

2voto

Grzenio Points 16802

Il est très difficile de reproduire des bogues multithreads - il faut généralement exécuter le code de test plusieurs fois (des milliers de fois) et disposer d'une vérification automatisée qui signalera si le bogue se produit. Vous pouvez essayer d'ajouter un court Thread.Sleep(10) entre certaines lignes, mais là encore, il n'est pas toujours garanti que vous obtiendrez les mêmes problèmes que sans cela.

Les barrières de mémoire ont été introduites pour les personnes qui ont besoin d'optimiser les performances de bas niveau de leur code multithread. Dans la plupart des cas, il est préférable d'utiliser d'autres primitives de synchronisation, telles que volatile ou lock.

0voto

dsolimano Points 5065

Si vous touchez des données provenant de deux fils différents, cela peut se produire. C'est l'une des astuces utilisées par les processeurs pour augmenter leur vitesse. Vous pourriez construire des processeurs qui ne font pas cela, mais ils seraient beaucoup plus lents, donc personne ne le fait plus. Vous devriez probablement lire quelque chose comme Hennessey et Patterson pour reconnaître tous les différents types de conditions de course.

J'utilise toujours une sorte d'outil de niveau supérieur comme un moniteur ou un verrou, mais en interne, ils font quelque chose de similaire ou sont mis en œuvre avec des barrières.

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