43 votes

Dois-je verrouiller ou marquer comme volatile l'accès à un simple indicateur booléen en C# ?

Disons que vous avez une opération simple qui s'exécute sur un fil d'arrière-plan. Vous voulez fournir un moyen d'annuler cette opération, vous créez donc un drapeau booléen que vous mettez à true à partir du gestionnaire d'événement de clic d'un bouton d'annulation.

private bool _cancelled;

private void CancelButton_Click(Object sender ClickEventArgs e)
{
    _cancelled = true;
}

Maintenant, vous définissez le drapeau d'annulation à partir du fil de l'interface graphique, mais vous le lisez à partir du fil d'arrière-plan. Devez-vous verrouiller avant d'accéder au bool ?

Auriez-vous besoin de faire cela (et évidemment de verrouiller aussi le gestionnaire d'événement du clic du bouton) :

while(operationNotComplete)
{
    // Do complex operation

    lock(_lockObject)
    {
        if(_cancelled)
        {
            break;
        }
    }
}

Ou est-il acceptable de faire cela (sans verrou) :

while(!_cancelled & operationNotComplete)
{
    // Do complex operation
}

Ou pourquoi ne pas marquer la variable _cancelled comme volatile. Est-ce nécessaire ?

[Je sais qu'il existe la classe BackgroundWorker avec sa méthode intégrée CancelAsync(), mais ce qui m'intéresse ici, c'est la sémantique et l'utilisation du verrouillage et de l'accès threadé aux variables, pas l'implémentation spécifique, le code est juste un exemple].

Il semble y avoir deux théories.

1) Parce qu'il s'agit d'un simple type intégré (et l'accès aux types intégrés est atomique en .net) et parce que nous n'écrivons qu'à un seul endroit et ne lisons que sur le fil d'arrière-plan, il n'est pas nécessaire de verrouiller ou de marquer comme volatile.
2) Vous devez le marquer comme volatile car si vous ne le faites pas, le compilateur peut optimiser la lecture dans la boucle while parce qu'il pense que rien n'est capable de modifier la valeur.

Quelle est la technique correcte ? (Et pourquoi ?)

[Il semble y avoir deux écoles de pensée clairement définies et opposées sur ce sujet. Je cherche une réponse définitive sur ce sujet, alors s'il vous plaît, si possible, postez vos raisons et citez vos sources avec votre réponse].

39voto

Marc Gravell Points 482669

Tout d'abord, l'enfilage est délicat ;-p

Oui, en dépit de toutes les rumeurs contraires. es requis pour soit utiliser lock o volatile (mais pas les deux) lors de l'accès à un bool à partir de plusieurs fils.

Pour les types et accès simples tels qu'un drapeau de sortie ( bool ), alors volatile est suffisant - cela permet de s'assurer que les threads ne mettent pas en cache la valeur dans leurs registres (ce qui signifie que l'un des threads ne voit jamais les mises à jour).

Pour des valeurs plus importantes (où l'atomicité est un problème), ou lorsque vous voulez synchroniser un fichier séquence d'opérations (un exemple typique étant l'accès au dictionnaire "if not exists and add"), une lock est plus polyvalent. Elle agit comme une barrière de mémoire, et vous offre donc toujours la sécurité des fils, mais fournit d'autres fonctionnalités telles que le pulse/wait. Notez que vous ne devriez pas utiliser un lock sur un type de valeur ou un string ; ni Type o this ; la meilleure option est d'avoir votre propre objet de verrouillage comme champ ( readonly object syncLock = new object(); ) et de verrouiller sur ce point.

Pour un exemple de la façon dont cela se casse la figure (c.-à-d. en bouclant indéfiniment) si vous ne synchronisez pas - voir ici .

Pour couvrir plusieurs programmes, une primitive de l'OS comme un Mutex o *ResetEvent peut également être utile, mais c'est une surcharge pour un seul exe.

6voto

bruno conde Points 28120

_cancelled doit être volatile . (si vous ne choisissez pas de verrouiller)

Si un thread modifie la valeur de _cancelled les autres fils de discussion pourraient ne pas voir le résultat mis à jour.

Aussi, je pense que les opérations de lecture/écriture de _cancelled sont atomique :

La section 12.6.6 de la spécification CLI indique : "Un CLI conforme doit garantir que l'accès en lecture et en écriture à des correctement alignés, dont la taille ne dépasse pas que la taille native du mot est atomique lorsque tous les accès en écriture à un emplacement emplacement ont la même taille."

5voto

Daniel Brückner Points 36242

Le verrouillage n'est pas nécessaire parce que vous avez un scénario à auteur unique et qu'un champ booléen est une structure simple sans risque de corruption de l'état ( alors qu'il était possible d'obtenir une valeur booléenne qui n'est ni fausse ni vraie ). Mais vous devez marquer le champ comme volatile pour empêcher le compilateur d'effectuer certaines optimisations. Sans l'option volatile le compilateur pourrait mettre en cache la valeur dans un registre pendant l'exécution de votre boucle sur votre fil d'exécution et, par conséquent, la boucle ne reconnaîtrait jamais la valeur modifiée. Cet article de MSDN répond à ce problème. Bien qu'il soit nécessaire de verrouiller, un verrouillage aura le même effet que le marquage du champ volatile .

2voto

Adam Robinson Points 88472

Pour la synchronisation des threads, il est recommandé d'utiliser l'une des méthodes suivantes EventWaitHandle les classes, telles que ManualResetEvent . Bien qu'il soit légèrement plus simple d'utiliser un simple drapeau booléen comme vous le faites ici (et oui, vous voudriez le marquer en tant que volatile ), je pense qu'il est préférable de s'entraîner à utiliser les outils d'enfilage. Pour vos besoins, vous feriez quelque chose comme ceci...

private System.Threading.ManualResetEvent threadStop;

void StartThread()
{
    // do your setup

    // instantiate it unset
    threadStop = new System.Threading.ManualResetEvent(false); 

    // start the thread
}

Dans votre fil..

while(!threadStop.WaitOne(0) && !operationComplete)
{
    // work
}

Puis dans l'interface graphique pour annuler...

threadStop.Set();

1voto

hughdbrown Points 15770

Regardez en haut Interlocked.Exchange() . Il effectue une copie très rapide dans une variable locale qui peut être utilisée pour la comparaison. Elle est plus rapide que lock().

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