8
votes

Est-ce que cette utilisation du fil de la liste générique est-elle sécurisée?

J'ai un system.collections.generic.list code> sur lequel je n'aime jamais les éléments dans un rappel de la minuterie. La minuterie est redémarrée seulement après la fin de l'opération.

J'ai un system.collections.concurrent.concurrentqueue code> qui stocke des indices d'éléments ajoutés dans la liste ci-dessus. Cette opération de magasin est également toujours effectuée dans le même rappel de la minuterie décrit ci-dessus. P>

est une opération de lecture qui compte la file d'attente et accède aux éléments correspondants du fil de la liste Safe Safe? P>

code d'exemple : P>

private List<Object> items;
private ConcurrentQueue<int> queue;
private Timer timer;
private void callback(object state)
{
    int index = items.Count;
    items.Add(new object());
    if (true)//some condition here
        queue.Enqueue(index);
    timer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(-1));
}

//This can be called from any thread
public IEnumerable<object> AccessItems()
{
    foreach (var index in queue)
    {
        yield return items[index];
    }
}


5 commentaires

Quelle classe de minuterie utilisez-vous?


et non, ce n'est pas le fil sûr. ajout à éléments en la lecture d'un comportement indéfini.


System.threading.Timer. Techniquement, je conviens que c'est un comportement indéfini, mais je ne peux penser à une mise en œuvre raisonnable de la liste qui causerait un problème ici. Je suis inquiet des choses comme la cohérence de cache que je ne comprends pas trop bien.


1) CACHE COHÉRENCE PASSEZ DÉJÀ 2) Considérez une implémentation qui stocke l'ancien tableau dans une variable locale, remplace le champ avec la nouvelle matrice, puis copie les éléments.


@CODESINCHAOS Pouvez-vous élaborer comment la cohérences de cache casses cela?


4 Réponses :


6
votes

Big Edit

Le Concurrentqueue n'est sécurisé que par rapport aux opérations Enqueue (T) et T-dequeuse (). Vous faites une foreach sur elle et qui ne vous est pas synchronisé au niveau requis. Le plus gros problème dans votre cas particulier est le fait que l'énumération de la file d'attente (qui est une collection de sa propre droite) pourrait lancer la "collection" a été modifiée "une exception. Pourquoi est-ce le plus gros problème? Parce que vous ajoutez des choses à la file d'attente après , vous avez ajouté les objets correspondants à la liste (il existe également un bon besoin que la liste soit synchronisée, mais que + le plus gros problème est résolu avec un seul " balle"). Tout en énumérant une collection, il n'est pas facile d'avaler le fait qu'un autre thread la modifie (même si au niveau microscopique, la modification est un coffre-fort - Concurrentqueue suffit que).

Vous devez absolument synchroniser le Accès aux files d'attente (et la liste centrale pendant que vous y êtes) en utilisant un autre moyen de synchronisation (et je veux dire que vous pouvez également oublier Abount Concurrentqueue et utiliser une simple file d'attente ou même une liste depuis que vous ne ressusez jamais des choses).

Faites donc quelque chose comme: xxx

et dans l'accessitems: xxx

et, Basé sur toute ma compréhension des cas dans laquelle votre application accède à la liste et aux différentes files d'attente, elle devrait être sûre à 100%.

fin de gros modification

Tout d'abord: < un href = "http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call-thread-safe.aspx" rel = "nofollow"> Quelle est cette chose que vous appelez fil-coffre-fort? par Eric Lippert

dans votre cas particulier, je suppose que la réponse est non .

Ce n'est pas le cas que les incohérences peuvent s'arrise dans Le contexte global (la liste réelle).

au lieu de cela, il est possible que les lecteurs réels (qui puissent très bien "entrer en collision" avec l'écrivain unique) se retrouvent avec des incohérences en elles-mêmes (leurs propres piles signifiant: variables locales de toutes les méthodes, paramètres et leur partie isolée logiquement du tas)).

La possibilité de tels incohérences "par thread" (le nième thread veut apprendre le nombre d'éléments de la liste et découvre que la valeur est 39404999 Bien que, en réalité, vous ne ajoute que 3 valeurs) suffisent à déclarer que, d'une manière générale, cette architecture est non thread-coffre-fort (bien que vous ne modifiez pas réellement la liste accessible à l'échelle mondiale. , simplement en le lisant de manière imparfaite).

Je vous suggère d'utiliser ReaderWriterLockSlim classe. Je pense que vous trouverez que cela correspond à vos besoins: xxx


8 commentaires

Articles [Index] est la seule ligne que je suis inquiet. Dans cet exemple spécifique, cela peut-il lancer une exception ou renvoyer le mauvais article?


Parlons-nous de la modification que j'ai faite à votre méthode Accessitems ? Si vous voulez dire que vous voulez dire SnapShot [Index], alors, oui ... Vous ne jouez pas du tout. Pourquoi utilisez-vous réellement la file d'attente?


Hei .. ne vous inquiétez pas, j'ai eu une solution, vérifiez la nouvelle édition de ma réponse


Non, je demande mon code d'origine. éléments [index] est le seul endroit où il pourrait y avoir un problème


S'il vous plaît vérifier l'édition que j'ai faite à ma réponse. Je ne suis pas sûr que c'est le seul endroit .. c'est un endroit. Ça c'est sûr. Les modifications sont mineures (ajoutez simplement l'objet ReaderWrriterLockSlim et gardez votre code avec celui-ci - Modification de la nature Itératrice de la 2e méthode (plus de rendement de rendement)


BTW: On dirait que vous ne ressemblez jamais à votre file d'attente. Faites-vous tout cela juste pour assurer la commande dans la liste? (Il est déjà assuré) ou vous ne mentionnez pas tout ce que vous êtes sur le point de faire avec les deux collections?


Quel autre endroit pourrait-il y avoir un problème? Dans mon code actuel, il y a plusieurs files d'attente, pas seulement. Chaque file d'attente contient différents indices en fonction de la logique. Chaque file d'attente est donc une vue différente de la liste. La méthode Accessitems comporte un autre paramètre qui spécifie quelle file d'attente à utiliser. Les files d'attente ne sont jamais dequelles.


Laissez-nous Continuez cette discussion en chat



1
votes

Non parce que vous lisez et écrivez à / depuis le même objet simultanément. Ceci n'est pas documenté pour être en sécurité, vous pouvez donc vous assurer qu'il est sûr. Ne le faites pas.

Le fait que c'est en fait dangereux à partir de .NET 4.0 signifie rien, BTW. Même si c'était en sécurité selon le réflecteur, cela pourrait changer à tout moment. Vous ne pouvez pas compter sur la version actuelle pour prédire les versions futures.

N'essayez pas de vous échapper avec des tours comme ça. Pourquoi ne pas simplement le faire d'une manière évidemment sûre?

En tant que note latérale: Deux rappels de la minuterie peuvent exécuter en même temps, votre code est donc doublement brisé (multiples écrivains). N'essayez pas de tirer des tours avec des fils.


1 commentaires

Accepter avec le point "ne tirez pas". Je planifie la minuterie pour le prochain rappel uniquement lorsque le rappel actuel est terminé. Deux rappels ne peuvent donc pas exécuter en même temps dans ce cas.



9
votes

est une opération de lecture qui compte la file d'attente et accède aux éléments correspondants du fil de la liste?

est-ce documenté comme étant le fil de sécurité?

Si non, alors il est stupide de le traiter comme un fil sûr, même si c'est dans cette mise en œuvre par accident . La sécurité du fil doit être par design .

Le partage de la mémoire à travers les threads est une mauvaise idée en premier lieu; Si vous ne le faites pas, vous n'avez pas à vous demander si l'opération est en sécurité.

Si vous devez le faire, utilisez une collection conçue pour l'accès à la mémoire partagée.

Si vous ne pouvez pas faire cela, alors utiliser un verrou . Les serrures sont bon marché si non compris.

Si vous avez un problème de performance, car vos serrures sont affirmées tout le temps, résolvez ce problème en modifiant votre architecture de threading plutôt que d'essayer de faire des choses dangereuses et stupides comme le code Low-Lock. Personne n'écrit correctement le code à verrouillage à l'exception d'une poignée d'experts. (Je ne suis pas l'un d'entre eux; Je n'écris pas non plus de code à verrouillage.)

Même si la liste est redimensionnée lorsqu'elle est indexée, j'accélère qu'à un élément qui existe déjà, de sorte qu'il n'a pas de problème, qu'il s'agisse de la lecture de l'ancien tableau ou de la nouvelle matrice.

C'est la mauvaise façon d'y penser. La bonne façon de penser est:

Si la liste est redimensionnée, les structures de données internes de la liste sont mutées. Il est possible que la structure de données interne soit mutée dans une forme incohérente à mi-chemin de la mutation, qui sera rendue compatible au moment de la fin de la mutation. Par conséquent, mon lecteur peut voir cet état incohérent d'un autre fil, ce qui rend le comportement de mon programme total imprévisible . Cela pourrait s'écraser, cela pourrait aller dans une boucle infinie, cela pourrait corrompre d'autres structures de données, je ne sais pas, car Je suis courant qui suppose un état cohérent dans un monde avec un État incohérent .


6 commentaires

Bref, précis et au point (alors que le mien était long, non structuré et incompréhensible :))


@ERIC Merci pour la réponse et je n'utiliserai pas ce code dans la production. Mais ai-je raison de croire que le seul problème avec ce code est dans le redimensionnement de la liste? Ou y a-t-il d'autres problèmes tels que l'accès au champ de réseau potentiellement sorti de la part de la forach?


@ K.M.: Lorsque vous conjecture, il existe des problèmes de cohérence potentiels. Rien n'empêche des lectures non volatiles de comptage et les matrices d'être déplacées vers l'avant et vers l'arrière dans le temps les uns des autres.


@ERICLIPPERT Dites-vous qu'un index incorrect pourrait être inséré dans la file d'attente, même si le rappel n'est jamais appelé simultanément de différents threads? Ce serait très surprenant pour moi.


Utilisation du code qui n'est pas conçu pour un accès multithread comme s'il était souvent résulte des résultats très surprenants.


@ K.m.: Je dis que vous avez deux endroits, on stocke la valeur de la propriété Count et un élément de tableau. Vous lisez et écrivez les deux emplacements sur un fil et lisez à la fois sur un autre fil. (N'oubliez pas que le lecteur doit savoir que l'indice est inférieur au nombre de comptes afin qu'il puisse jeter si ce n'est pas le cas.) Je ne vois rien qui empêche les lectures et écrit de devenir sans ordonnance les uns des autres, qui m'inquiète. Quels arrêtnent-ils d'être incompatibles avec si la valeur a été écrite dans le tableau?



1
votes

C'est le fil-lafish. La déclaration de forach utilise la méthode ConcurrentQueue.getenumerator (). Qui promet:

L'énumération représente un instantané instantané du contenu de la file d'attente. Il ne reflète aucune mise à jour de la collection après que Getenumerator a été appelée. L'énumérateur est sûr d'utiliser simultanément avec des lectures et écrites à la file d'attente.

Ce qui est une autre façon de dire que votre programme ne va pas exploser au hasard avec un message d'exception inscultable comme le type que vous obtiendrez lorsque vous utilisez la classe de la file d'attente. Méfiez-vous des conséquences cependant, implicite dans cette garantie est que vous recherchez une version rassis de la file d'attente. Votre boucle ne sera pas en mesure de voir aucun élément ajouté par un autre thread après que votre boucle ait commencé à exécuter. Ce genre de magie n'existe pas et est impossible à mettre en œuvre de manière cohérente. Que vous préférez ou non que votre programme est quelque chose que vous devrez réfléchir et ne peut être deviné de la question. Il est assez rare que vous puissiez l'ignorer complètement.

Votre utilisation de la liste <> est cependant totalement dangereuse.


5 commentaires

Merci pour l'info. J'ai commis une erreur dans ma réponse (en supposant que, puisque la plupart des implémentations Getenumerator généralement utilisés ne font pas ce que @Hans Passant a déclaré à propos de la mise en œuvre de Concurrentqueue, il est préférable de simplement synchroniser les choses vous-même et commencer à utiliser une file d'attente régulière au lieu de Concurrentqueue).


Oui, l'utilisation du code est conçue pour gérer la relâche. Je voulais être sûr que l'accès à la liste n'entraîne pas une exception ou un élément erroné en cours de lecture.


Il lit de la liste lorsque la liste est écrite (clairement dangereuse). Il écrit également de multiples threads (plusieurs invocations de rappels simultanées sont possibles avec les minuteries).


C'est dangereux lorsque vous utilisez la file d'attente. Concurrentqueue fournit des garanties de concurrence qui le rendent en sécurité.


Oh, d'accord, je n'ai pas vu ça. Aucune idée de ce qui essaie de faire, mais cela a certainement l'air de Borken.