Question

Je ne suis toujours pas sûr de savoir quand insérer un verrou dans du code. Ma règle générale consiste à envelopper une opération dans un verrou lorsqu'elle lit ou écrit dans une variable statique. Mais lorsqu'une variable statique est UNIQUEMENT en lecture (par exemple, c'est une lecture seule qui est définie lors de l'initialisation du type), son accès ne doit pas nécessairement être encapsulé dans une instruction de verrouillage, n'est-ce pas? J'ai récemment vu du code ressemblant à l'exemple suivant, et cela m'a fait penser qu'il pourrait y avoir des lacunes dans mes connaissances en multithreading:

class Foo
{
    private static readonly string bar = "O_o";

    private bool TrySomething()
    {
        string bar;

        lock(Foo.objectToLockOn)
        {
            bar = Foo.bar;          
        }       

        // Do something with bar
    }
}

Cela n’a aucun sens pour moi. Pourquoi y aurait-il des problèmes de simultanéité avec READING un registre?

De plus, cet exemple soulève une autre question. Est-ce que l'un d'entre eux est meilleur que l'autre? (Par exemple, deux exemples tiennent le verrou moins de temps?) Je suppose que je pourrais démonter le MSIL ...

class Foo
{
    private static string joke = "yo momma";

    private string GetJoke()
    {
        lock(Foo.objectToLockOn)
        {
            return Foo.joke;
        }
    }
}

vs.

class Foo
{
    private static string joke = "yo momma";

        private string GetJoke()
        {
            string joke;

            lock(Foo.objectToLockOn)
            {
                joke = Foo.joke;
            }

            return joke;
        }
}
Était-ce utile?

La solution

Comme aucun des codes que vous avez écrits ne modifie le champ statique après l’initialisation, aucun verrouillage n’est nécessaire. Le simple fait de remplacer la chaîne par une nouvelle valeur ne nécessitera pas non plus de synchronisation, sauf si la nouvelle valeur dépend des résultats de la lecture de l'ancienne valeur.

Les champs statiques ne sont pas les seuls éléments nécessitant une synchronisation. Toute référence partagée modifiable est vulnérable aux problèmes de synchronisation.

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        count++;
    }
}

Vous pouvez supposer que deux threads exécutant la méthode TrySomething pourraient convenir. Mais ce n'est pas.

  1. Le fil A lit la valeur de count (0) dans un registre pour pouvoir l'incrémenter.
  2. Changement de contexte! Le planificateur de threads décide que le thread A a eu suffisamment de temps d'exécution. Le fil suivant est le suivant:
  3. Le fil B lit la valeur du compte (0) dans un registre.
  4. Le fil B incrémente le registre.
  5. Le fil de discussion B enregistre le résultat (1) à compter.
  6. Le contexte revient à A.
  7. Le thread A recharge le registre avec la valeur count (0) enregistrée sur sa pile.
  8. Le fil A incrémente le registre.
  9. Le fil de discussion A enregistre le résultat (1) à compter.

Ainsi, même si nous avons appelé count ++ deux fois, la valeur de count vient de passer de 0 à 1. Permet de rendre le code thread-safe:

class Foo
{
    private int count = 0;
    private readonly object sync = new object();
    public void TrySomething()    
    {
        lock(sync)
            count++;
    }
}

Désormais, lorsque le thread A est interrompu, le thread B ne peut pas modifier le décompte, car il enfonce l'instruction de verrouillage, puis se bloque jusqu'à ce que le thread A libère la synchronisation.

Soit dit en passant, il existe un moyen alternatif d’incrémenter les threads en toute sécurité Int32s et Int64s:

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        System.Threading.Interlocked.Increment(ref count);
    }
}

En ce qui concerne la deuxième partie de votre question, je pense que je choisirais celle qui est la plus facile à lire, toute différence de performances sera négligeable. L’optimisation précoce est la racine de tous les maux, etc.

Pourquoi le threading est difficile

Autres conseils

La lecture ou l'écriture d'un champ de 32 bits ou moins est une opération atomique en C #. Autant que je sache, il n'est pas nécessaire de verrouiller le code que vous avez présenté.

Il me semble que le verrouillage est inutile dans votre premier cas. L'utilisation de l'initialiseur statique pour initialiser la barre garantit la sécurité des threads. Comme vous ne lisez que la valeur, il n’est pas nécessaire de la verrouiller. Si la valeur ne changera jamais, il n'y aura jamais de conflit, pourquoi verrouiller du tout?

Lectures sales?

À mon avis, vous devez vous efforcer de ne pas placer les variables statiques dans une position où elles doivent être lues / écrites depuis différents threads. Dans ce cas, ce sont essentiellement des variables globales libres de tout, et les variables globales sont presque toujours mauvaises.

Ceci étant dit, si vous placez une variable statique dans une telle position, vous voudrez peut-être verrouiller pendant une lecture, juste au cas où, rappelez-vous, un autre thread pourrait s'être noyé et avoir changé la valeur au cours de the read, et si c'est le cas, vous risquez de vous retrouver avec des données corrompues. Les lectures ne sont pas nécessairement des opérations atomiques, sauf si vous vous assurez qu'elles le sont par verrouillage. Même chose avec les écritures - ce ne sont pas toujours des opérations atomiques.

Modifier: Comme Mark l'a souligné, certaines primitives dans les lectures C # sont toujours atomiques. Mais soyez prudent avec les autres types de données.

Si vous écrivez simplement une valeur dans un pointeur, vous n'avez pas besoin de verrouiller, car cette action est atomique. En règle générale, vous devez verrouiller chaque fois que vous devez effectuer une transaction impliquant au moins deux actions atomiques (lecture ou écriture) qui dépendent du fait que l'état ne change pas entre le début et la fin.

Cela dit - je viens de la terre Java, où toutes les lectures et écritures de variables sont des actions atomiques. D’autres réponses suggèrent que .NET est différent.

En ce qui concerne votre "qui est le meilleur" question, ils sont les mêmes puisque la fonction n'est pas utilisée pour autre chose.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top