14 votes

C# : le verrouillage est-il nécessaire lors de l'échange de références de variables dans une application multithread ?

J'ai une application où je veux que plusieurs threads lisent une liste. Je veux mettre à jour la liste avec de nouvelles données périodiquement. Lorsque la liste est mise à jour, je me dis que je peux créer une nouvelle liste et la remplacer par l'ancienne. Exemple :

private List<string> _list = new List<string>();
private void UpdateList()
{
    var newList = new List<string>(QueryList(...));
    _list = newList;
}

private void ThreadRun()
{
    foreach (var item in _list)
    {
        // process item...
    }
}

Dans la méthode UpdateList, une nouvelle liste est créée et la référence _list est échangée avec la nouvelle liste. D'après moi, tout thread existant conservera la référence à l'ancienne liste (ce qui me convient), et tout nouveau thread récupérera la nouvelle liste. Finalement, tous les threads se termineront et l'ancienne liste sera finalement ramassée. Un verrouillage est-il nécessaire dans ce code, ou dois-je faire attention à quelque chose pour garantir un accès multithread sûr ?

9voto

Jon Hanna Points 40291

Pour être sûr que ni la stagnation ni les optimisations ne vous nuisent, utilisez Interlocked.Exchange() pour mettre à jour le champ. Vous disposerez alors d'une barrière de mémoire appropriée pour l'écriture, sans avoir besoin de recourir à la fonction volatile à chaque lecture.

4voto

Nicholas Butler Points 12630

Vous rendez les instances de la liste immuables et c'est seulement l'état mutable qui pose problème.

Votre code est bon, mais vous devriez envisager de marquer _list como volatile . Cela signifie que toutes les lectures obtiennent la dernière version et que le compilateur / jitter / cpu n'optimise pas les lectures.

Vous pouvez également mettre un Thread.MemoryBarrier() juste avant d'assigner la nouvelle liste pour s'assurer que toutes les écritures dans la nouvelle liste sont validées avant de la publier, mais ce n'est pas un problème sur l'architecture x86.

3voto

rich.okelly Points 24144

L'affectation est atomique, votre code est bon. Vous pouvez envisager de marquer _list comme volatile afin de s'assurer que tous les fils qui demandent la variable obtiennent la version la plus récente :

private volatile List<string> _list = new List<string>();

3voto

Gayot Fow Points 5314

Si vous utilisez .NET 4 ou plus, vous pouvez utiliser les nouvelles collections thread safe...

BlockingCollection<string> list  = new BlockingCollection<string>();

La classe BlockingCollection possède des méthodes à sécurité thread pour ajouter et supprimer des membres, un peu comme le modèle de conception Producer Consumer.

Les threads peuvent ajouter des membres de la liste et d'autres threads peuvent en supprimer, sans surcharge de programmation.

En outre, il vous permet de faire des choses comme ceci...

    foreach (string i in list)
    {
        list.Take();
        list.Add(i + 200);
    }

Ce code supprime et ajoute à une collection pendant que son énumérateur fonctionne, ce qui ne pouvait jamais être fait en c# avant .NET 4. Il n'est pas nécessaire de le déclarer volatile.

    foreach (string i in list)
    {
        new Task(() =>list.Take()).Start();
        new Task(() =>list.Add(i + 200)).Start();
    }

Dans cet extrait, N*2 threads sont lancés qui opèrent tous sur la même liste...

Le comportement différent implicite dans l'utilisation de Concurrnt Collections peut vous éviter d'avoir deux listes.

3voto

Ian Ringrose Points 19115

Vous devez vous documenter sur le modèle de mémoire .net, car en général, l'ordre des droits n'est pas défini sur un processeur. Ainsi, une nouvelle valeur de _list peut être écrite dans la mémoire partagée, alors qu'une partie d'un nouvel objet créé, pointé par _list, est toujours dans la file d'attente d'écriture du processeur.

Le réordonnancement des droits est un problème difficile à résoudre. Filet.MemoryBarrier () ; comme ceci.

private void UpdateList() 
{     
    var newList = new List<string>(QueryList(...));   
    Thread.MemoryBarrier();  
    _list = newList; 
} 

Ver el Threading en C# page web par Joseph Albahari pour plus de détails

Cependant, je pense que sur la plupart des processeurs "normaux", votre code fonctionnera tel qu'il est écrit.

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