Qu'est-ce qui rend une méthode sûre pour les threads? Quelles sont les règles?

156

Existe-t-il des règles / directives générales sur ce qui rend une méthode thread-safe? Je comprends qu'il y a probablement un million de situations ponctuelles, mais qu'en est-il en général? Est-ce simple?

  1. Si une méthode accède uniquement aux variables locales, elle est thread-safe.

Est-ce que c'est ça? Cela s'applique-t-il également aux méthodes statiques?

Une réponse, fournie par @Cybis, était:

Les variables locales ne peuvent pas être partagées entre les threads car chaque thread obtient sa propre pile.

Est-ce également le cas des méthodes statiques?

Si une méthode reçoit un objet de référence, cela brise-t-il la sécurité des threads? J'ai fait quelques recherches, et il y a beaucoup de choses sur certains cas, mais j'espérais être en mesure de définir, en utilisant seulement quelques règles, des directives à suivre pour m'assurer qu'une méthode est thread-safe.

Donc, je suppose que ma question ultime est: "Y a-t-il une courte liste de règles qui définissent une méthode thread-safe? Si oui, quelles sont-elles?"

EDIT
Beaucoup de bons points ont été soulevés ici. Je pense que la vraie réponse à cette question est: "Il n'y a pas de règles simples pour garantir la sécurité des threads." Cool. Bien. Mais en général, je pense que la réponse acceptée fournit un bon résumé court. Il y a toujours des exceptions. Ainsi soit-il. Je peux vivre avec ça.

Bob Horn
la source
59
Vous ne pourrez pas accéder aux variables qui sont également accessibles par d'autres threads sans verrou.
Hans Passant
4
Hanthe pathant s'est transformé en igor!
Martin James
3
Aussi ... «Tu n'accéderas pas aux variables qui sont également accessibles par d'autres threads sans verrou» - sauf si cela n'a pas d'importance si la valeur lue n'est parfois pas la dernière ou est en fait grossièrement incorrecte.
Martin James
Voici un joli blog d'Eric pour vous emmener dans un tourbillon.
RBT

Réponses:

140

Si une méthode (instance ou statique) fait uniquement référence à des variables de portée dans cette méthode, elle est thread-safe car chaque thread a sa propre pile:

Dans ce cas, plusieurs threads peuvent appeler ThreadSafeMethodsimultanément sans problème.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

Cela est également vrai si la méthode appelle une autre méthode de classe qui ne fait référence qu'à des variables de portée locale:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

Si une méthode accède à des propriétés ou des champs (d'état de l'objet) (instance ou statique), vous devez utiliser des verrous pour vous assurer que les valeurs ne sont pas modifiées par un thread différent.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

Vous devez être conscient que tous les paramètres transmis à la méthode qui ne sont ni un struct ni immuable peuvent être mutés par un autre thread en dehors de la portée de la méthode.

Pour garantir une concurrence d'accès appropriée, vous devez utiliser le verrouillage.

pour plus d'informations, consultez la référence C # de l'instruction de verrouillage et ReadWriterLockSlim .

lock est surtout utile pour fournir une fonctionnalité à la fois,
ReadWriterLockSlimest utile si vous avez besoin de plusieurs lecteurs et d'écrivains uniques.

Trevor Pilley
la source
15
Dans le troisième exemple private string someValue;n'est pas staticsi chaque instance obtiendra une copie distincte de cette variable. Alors, pouvez-vous expliquer en quoi ce n'est pas thread-safe?
Bharadwaj
29
@Bharadwaj s'il y a une instance de la Thingclasse accessible par plusieurs threads
Trevor Pilley
112

Si une méthode accède uniquement aux variables locales, elle est thread-safe. Est-ce que c'est ça?

Absolument pas. Vous pouvez écrire un programme avec une seule variable locale accessible à partir d'un seul thread qui n'est cependant pas threadsafe:

https://stackoverflow.com/a/8883117/88656

Cela s'applique-t-il également aux méthodes statiques?

Absolument pas.

Une réponse, fournie par @Cybis, était: "Les variables locales ne peuvent pas être partagées entre les threads car chaque thread obtient sa propre pile."

Absolument pas. La caractéristique distinctive d'une variable locale est qu'elle n'est visible que depuis l'étendue locale , et non qu'elle est allouée sur le pool temporaire . Il est parfaitement légal et possible d'accéder à la même variable locale à partir de deux threads différents. Vous pouvez le faire en utilisant des méthodes anonymes, des lambdas, des blocs d'itérateur ou des méthodes asynchrones.

Est-ce également le cas des méthodes statiques?

Absolument pas.

Si une méthode reçoit un objet de référence, cela brise-t-il la sécurité des threads?

Peut être.

J'ai fait quelques recherches, et il y a beaucoup de choses sur certains cas, mais j'espérais être en mesure de définir, en utilisant seulement quelques règles, des directives à suivre pour m'assurer qu'une méthode est thread-safe.

Vous allez devoir apprendre à vivre avec la déception. C'est un sujet très difficile.

Donc, je suppose que ma question ultime est: "Y a-t-il une courte liste de règles qui définissent une méthode thread-safe?

Nan. Comme vous l'avez vu dans mon exemple plus tôt, une méthode vide peut être non thread-safe . Vous pourriez aussi bien demander "existe-t-il une courte liste de règles garantissant qu'une méthode est correcte ". Non, il n'y en a pas. La sécurité des fils n'est rien de plus qu'un type d'exactitude extrêmement compliqué.

De plus, le fait que vous posiez la question indique votre malentendu fondamental sur la sécurité des fils. La sécurité des threads est une propriété globale et non locale d'un programme. La raison pour laquelle il est si difficile de bien faire est que vous devez avoir une connaissance complète du comportement de threading de l'ensemble du programme afin d'assurer sa sécurité.

Encore une fois, regardez mon exemple: chaque méthode est triviale . C'est la façon dont les méthodes interagissent les unes avec les autres à un niveau «global» qui rend le programme dans l'impasse. Vous ne pouvez pas regarder toutes les méthodes et les cocher comme "sûres" et vous attendre à ce que tout le programme soit sûr, pas plus que vous ne pouvez en conclure que parce que votre maison est faite de briques à 100% non creuses, la maison est également non creux. Le creux d'une maison est une propriété globale de l'ensemble, pas un agrégat des propriétés de ses parties.

Eric Lippert
la source
15
Déclaration de base: la sécurité des threads est une propriété globale et non locale d'un programme.
Greg D
5
@BobHorn: class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } Appelez M (), puis appelez C.getter et C.setter sur deux threads différents. La variable locale peut maintenant être écrite et lue sur deux threads différents, même s'il s'agit d'un local. Encore une fois: la caractéristique déterminante d'une variable locale est qu'elle est locale , non qu'elle se trouve sur la pile du thread .
Eric Lippert
3
@BobHorn: Je pense que cela a plus à voir avec la compréhension de nos outils à un niveau tel que nous pouvons en parler avec autorité et connaissance. Par exemple, la question originale trahissait un manque de compréhension de ce qu'est une variable locale. Cette erreur est assez courante, mais le fait est qu'il s'agit d'une erreur et doit être corrigée. La définition d'une variable locale est assez précise et doit être traitée en conséquence. :) Ce n'est pas une réponse pour les «gens» qui ne comprennent pas que des cas pathologiques existent. Il s'agit d'une clarification afin que vous puissiez comprendre ce que vous avez réellement demandé. :)
Greg D
7
@EricLippert: La documentation MSDN pour de nombreuses classes déclare que «les membres statiques publics (partagés en Visual Basic) de ce type sont thread-safe. Les membres de l'instance ne sont pas garantis d'être thread-safe. ' ou parfois «Ce type est thread-safe». (par exemple, String), comment ces garanties peuvent-elles être faites lorsque la sécurité des threads est une préoccupation mondiale?
Mike Zboray
3
@mikez: Bonne question. Soit ces méthodes ne mutent aucun état, soit lorsqu'elles le font, elles mutent l'état en toute sécurité sans verrous, ou, si elles utilisent des verrous, elles le font d'une manière qui garantit que l '«ordre de verrouillage» global n'est pas violé. Les chaînes sont des structures de données immuables dont les méthodes ne mutent rien, donc la chaîne peut être facilement sécurisée.
Eric Lippert
12

Il n'y a pas de règle absolue.

Voici quelques règles pour sécuriser les threads de code dans .NET et pourquoi ce ne sont pas de bonnes règles:

  1. La fonction et toutes les fonctions qu'elle appelle doivent être pures (pas d'effets secondaires) et utiliser des variables locales. Bien que cela rende votre code thread-safe, il y a aussi très peu de choses intéressantes que vous pouvez faire avec cette restriction dans .NET.
  2. Toute fonction qui opère sur un objet commun doit locksur une chose commune. Tous les verrous doivent être effectués dans le même ordre. Cela rendra le thread de code sûr, mais il sera incroyablement lent, et vous pourriez aussi bien ne pas utiliser plusieurs threads.
  3. ...

Il n'y a pas de règle qui rend le thread de code sûr, la seule chose que vous pouvez faire est de vous assurer que votre code fonctionnera quel que soit le nombre de fois qu'il est activement exécuté, chaque thread peut être interrompu à tout moment, chaque thread étant en cours son propre état / emplacement, et ce pour chaque fonction (statique ou non) qui accède aux objets communs.

comte
la source
10
Les verrous n'ont pas besoin d'être lents. Les serrures sont incroyablement rapides; un verrou incontesté est de l'ordre de grandeur de dix à cent nanosecondes . Les verrous contestés sont bien sûr arbitrairement lents; si vous êtes ralenti parce que vous contestez des verrous, réorganisez le programme pour supprimer le conflit .
Eric Lippert
2
Je suppose que je n'ai pas réussi à rendre le n ° 2 assez clair. J'essayais de dire qu'il existe un moyen de sécuriser les threads: mettre des verrous autour de chaque accès à des objets courants. Et en supposant qu'il y a plusieurs threads, cela générera des conflits et les verrous ralentiront le tout. Lors de l'ajout de verrous, on ne peut pas simplement ajouter des verrous aveuglément, ils doivent être placés dans de très bons endroits stratégiques.
earlNameless