web-dev-qa-db-fra.com

Propriétés thread-safe en C #

J'essaie de créer des propriétés thread-safe en C # et je veux m'assurer que je suis sur le bon chemin - voici ce que j'ai fait -

private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice 
{
    get
    {
        lock (AvgBuyPriceLocker)
        {
            return _AvgBuyPrice;
        }
    }
    set
    {
        lock (AvgBuyPriceLocker)
        {
            _AvgBuyPrice = value;
        }
    }
}

En lisant cet article, il semblerait que ce ne soit pas la bonne façon de le faire -

Sécurité des threads C # avec get/set

cependant, cet article semble suggérer le contraire,

http://www.codeproject.com/KB/cs/Synchronized.aspx

Quelqu'un at-il une réponse plus définitive?

Éditer:

La raison pour laquelle je veux faire le Getter/Setter pour cette propriété est b/c.

public class PLTracker
{

    public PLEvents Events;

    private readonly object AvgBuyPriceLocker = new object();
    private double _AvgBuyPrice;
    private double AvgBuyPrice 
    {
        get
        {
            lock (AvgBuyPriceLocker)
            {
                return _AvgBuyPrice;
            }
        }
        set
        {
            lock (AvgBuyPriceLocker)
            {
                Events.AvgBuyPriceUpdate(value);
                _AvgBuyPrice = value;
            }
        }
    }
}

public class PLEvents
{
    public delegate void PLUpdateHandler(double Update);
    public event PLUpdateHandler AvgBuyPriceUpdateListener;

    public void AvgBuyPriceUpdate(double AvgBuyPrice)
    {
        lock (this)
        {
            try
            {
                if (AvgBuyPriceUpdateListener!= null)
                {
                    AvgBuyPriceUpdateListener(AvgBuyPrice);
                }
                else
                {
                    throw new Exception("AvgBuyPriceUpdateListener is null");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Je suis assez nouveau pour sécuriser mon fil de code, alors n'hésitez pas à me dire si je m'y prend de la mauvaise façon!

Volonté

33
William

Puisque vous avez une valeur primitive, ce verrouillage fonctionnera correctement - le problème dans l'autre question était que la valeur de la propriété était une classe plus complexe (un type de référence mutable) - le verrouillage protégera l'accès et la récupération de l'instance de la valeur double détenue par ta classe.

Si la valeur de votre propriété est un type de référence mutable, le verrouillage ne protégera pas contre le changement de l'instance de classe une fois récupérée à l'aide de ses méthodes, ce que l'autre affiche voulait qu'il fasse.

19
BrokenGlass

Les verrous, comme vous les avez écrits, sont inutiles. Le thread qui lit la variable, par exemple, va:

  1. Acquérir le verrou.
  2. Lisez la valeur.
  3. Relâchez le verrou.
  4. Utilisez la valeur de lecture en quelque sorte.

Il n'y a rien pour empêcher un autre thread de modifier la valeur après l'étape 3. Comme l'accès aux variables dans .NET est atomique (voir la mise en garde ci-dessous), le verrou ne fait pas grand-chose ici: il suffit d'ajouter une surcharge. Contrairement à l'exemple déverrouillé:

  1. Lisez la valeur.
  2. Utilisez la valeur de lecture en quelque sorte.

Un autre thread peut modifier la valeur entre les étapes 1 et 2, ce qui n'est pas différent de l'exemple verrouillé.

Si vous voulez vous assurer que l'état ne change pas lorsque vous effectuez un traitement, vous devez lire la valeur et effectuer le traitement en utilisant cette valeur dans le contexte du verrou:

  1. Acquérir le verrou.
  2. Lisez la valeur.
  3. Utilisez la valeur de lecture en quelque sorte.
  4. Relâchez le verrou.

Cela dit, il y a des cas où vous devez verrouiller lors de l'accès à une variable. Celles-ci sont généralement dues à des raisons liées au processeur sous-jacent: une variable double ne peut pas être lue ou écrite comme une instruction unique sur une machine 32 bits, par exemple, vous devez donc verrouiller (ou utiliser une stratégie alternative) pour vous assurer une valeur corrompue n'est pas lue.

26
Paul Ruane

La sécurité des threads n'est pas quelque chose que vous devez ajouter à vos variables, c'est quelque chose que vous devez ajouter à votre "logique". Si vous ajoutez des verrous à toutes vos variables, votre code ne sera toujours pas nécessairement thread-safe, mais il sera lent comme l'enfer. Pour écrire un programme thread-safe, examinez votre code et décidez où plusieurs threads pourraient utiliser les mêmes données/objets. Ajoutez des verrous ou d'autres mesures de sécurité à tous ces endroits critiques.

Par exemple, en supposant le bit de pseudo-code suivant:

void updateAvgBuyPrice()
{
    float oldPrice = AvgBuyPrice;
    float newPrice = oldPrice + <Some other logic here>
    //Some more new price calculation here
    AvgBuyPrice = newPrice;
}

Si ce code est appelé à partir de plusieurs threads en même temps, votre logique de verrouillage est inutile. Imaginez le thread A obtenir AvgBuyPrice et faire quelques calculs. Maintenant, avant qu'il ne soit fait, le thread B obtient également l'AvgBuyPrice et commence les calculs. Le thread A en attendant est terminé et affectera la nouvelle valeur à AvgBuyPrice. Cependant, quelques instants plus tard, il sera remplacé par le thread B (qui utilisait toujours l'ancienne valeur) et le travail du thread A a été complètement perdu.

Alors, comment résolvez-vous cela? Si nous devions utiliser des verrous (ce qui serait la solution la plus laide et la plus lente, mais la plus simple si vous débutez avec le multithreading), nous devons mettre toute la logique qui modifie AvgBuyPrice dans les verrous:

void updateAvgBuyPrice()
{
    lock(AvgBuyPriceLocker)
    {
        float oldPrice = AvgBuyPrice;
        float newPrice = oldPrice + <Some other code here>
        //Some more new price calculation here
        AvgBuyPrice = newPrice;
    }
}

Maintenant, si le thread B veut faire les calculs alors que le thread A est toujours occupé, il attendra que le thread A soit terminé, puis fera son travail en utilisant la nouvelle valeur. Gardez à l'esprit cependant que tout autre code qui modifie également AvgBuyPrice devrait également verrouiller AvgBuyPriceLocker pendant qu'il fonctionne!

Pourtant, cela sera lent s'il est utilisé souvent. Les verrous sont chers et il existe de nombreux autres mécanismes pour éviter les verrous, il suffit de rechercher des algorithmes sans verrouillage.

16
Mart

La lecture et l'écriture des doubles sont de toute façon atomiques ( source ) la lecture et l'écriture de doubles ne sont pas atomiques et il serait donc nécessaire de protéger l'accès à un double à l'aide d'un verrou, mais pour de nombreux types, la lecture et l'écriture sont atomiques et donc les éléments suivants seraient tout aussi sûrs:

private float AvgBuyPrice
{
    get;
    set;
}

Mon point est que la sécurité des threads est plus complexe que la simple protection de chacune de vos propriétés. Pour donner un exemple simple, supposons que j'ai deux propriétés AvgBuyPrice et StringAvgBuyPrice:

private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }

Et supposons que je mette à jour le prix d'achat moyen ainsi:

this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();

Ce n'est clairement pas sûr pour les threads et les propriétés de protection individuelle de la manière ci-dessus ne seront d'aucune utilité. Dans ce cas, le verrouillage doit être effectué à un niveau différent plutôt qu'à un niveau par propriété.

6
Justin