In C# zou het beter zijn om Queue.Synchronized of lock() te gebruiken voor threadveiligheid?

Ik heb een Queue-object waarvan ik moet zorgen dat het thread-safe is. Zou het beter zijn om een ​​lock-object als dit te gebruiken:

lock(myLockObject)
{
//do stuff with the queue
}

Of wordt aanbevolen om Queue.Synchronized als volgt te gebruiken:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

Na het lezen van de MSDN-documenten zegt het dat ik Queue.Synchronized moet gebruiken om het thread-safe te maken, maar dan geeft het een voorbeeld met een lock-object. Uit het MSDN-artikel:

Om de draadveiligheid van de
Wachtrij, alle bewerkingen moeten worden uitgevoerd
alleen via deze wikkel.

Opsommen via een verzameling is
intrinsiek niet een thread-safe
procedure. Ook als er een collectie is
gesynchroniseerd, andere threads kunnen nog steeds
wijzig de collectie, waardoor
de enumerator om een ​​uitzondering te genereren.
Om draadveiligheid te garanderen tijdens:
opsomming, kunt u ofwel de
collectie gedurende de gehele
opsomming of vang de uitzonderingen
als gevolg van wijzigingen aangebracht door andere
discussies.

Als het aanroepen van Synchronized() geen thread-veiligheid garandeert, wat heeft het dan voor zin? Mis ik hier iets?


Antwoord 1, autoriteit 100%

Persoonlijk geef ik altijd de voorkeur aan vergrendelen. Het betekent dat ude granulariteit bepaalt. Als u alleen vertrouwt op de gesynchroniseerde wrapper, wordt elke afzonderlijke bewerking gesynchroniseerd, maar als u ooit meer dan één ding moet doen (bijvoorbeeld de hele verzameling herhalen), moet u toch vergrendelen. In het belang van de eenvoud, geef ik er de voorkeur aan om maar één ding te onthouden – op de juiste manier vergrendelen!

EDIT: Zoals opgemerkt in opmerkingen, als je kuntabstracties van een hoger niveau gebruiken, is dat geweldig. En als u vergrendeling welgebruikt, wees er dan voorzichtig mee – documenteer wat u verwacht waar te worden vergrendeld, en verkrijg/los vergrendelingen voor een zo kort mogelijke periode (meer voor correctheid dan prestaties). Vermijd het inroepen van onbekende code terwijl u een slot vasthoudt, vermijd geneste sloten enz.

In .NET 4 is er veelmeer ondersteuning voor abstracties op een hoger niveau (inclusief lock-free code). Hoe dan ook, ik zou nog steeds niet aanraden om de gesynchroniseerde wrappers te gebruiken.


Antwoord 2, autoriteit 70%

Er is een groot probleem met de Synchronizedmethoden in de oude collectiebibliotheek, in die zin dat ze synchroniseren op een te laag granulariteitsniveau (per methode in plaats van per werkeenheid).

Er is een klassieke raceconditie met een gesynchroniseerde wachtrij, hieronder weergegeven, waar je de Countcontroleert om te zien of het veilig is om uit de wachtrij te komen, maar dan levert de methode Dequeueeen uitzondering die aangeeft dat de wachtrij leeg is. Dit gebeurt omdat elke afzonderlijke bewerking thread-safe is, maar de waarde van Countkan veranderen tussen het moment waarop je het opvraagt ​​en het moment waarop je de waarde gebruikt.

object item;
if (queue.Count > 0)
{
    // at this point another thread dequeues the last item, and then
    // the next line will throw an InvalidOperationException...
    item = queue.Dequeue();
}

Je kunt dit veilig schrijven met een handmatige vergrendeling rond de hele werkeenheid (d.w.z. de telling controleren enhet item uit de wachtrij halen) als volgt:

object item;
lock (queue)
{
    if (queue.Count > 0)
    {
        item = queue.Dequeue();
    }
}

Omdat je niets veilig uit een gesynchroniseerde wachtrij kunt verwijderen, zou ik me er niet druk om maken en gewoon handmatige vergrendeling gebruiken.

.NET 4.0 zou een hele reeks correct geïmplementeerde thread-safe collecties moeten hebben, maar dat is helaas nog bijna een jaar verwijderd.


Antwoord 3, autoriteit 32%

Er is vaak een spanning tussen eisen voor ‘draadveilige collecties’ en de eis om meerdere bewerkingen op de collectie uit te voeren op een atomaire manier.

Dus Synchronized() geeft je een verzameling die zichzelf niet kapot maakt als meerdere threads er tegelijkertijd items aan toevoegen, maar het geeft je niet op magische wijze een verzameling die weet dat tijdens een opsomming niemand anders eraan hoeft te raken.

Naast opsomming, vereisen veelvoorkomende bewerkingen zoals “staat dit item al in de wachtrij? Nee, dan voeg ik het toe” ook synchronisatie die breder is dan alleen de wachtrij.


Antwoord 4, autoriteit 14%

Op deze manier hoeven we de wachtrij niet te vergrendelen om erachter te komen dat deze leeg was.

object item;
if (queue.Count > 0)
{
  lock (queue)
  {
    if (queue.Count > 0)
    {
       item = queue.Dequeue();
    }
  }
}

Antwoord 5, autoriteit 2%

Het lijkt me duidelijk dat het gebruik van een slot(…) {…} slot het juiste antwoord is.

Om de threadveiligheid van de wachtrij te garanderen, mogen alle bewerkingen alleen via deze wrapper worden uitgevoerd.

Als andere threads toegang krijgen tot de wachtrij zonder .Synchronized() te gebruiken, dan zit je in een stroomversnelling – tenzij al je toegang tot de wachtrij is geblokkeerd.

Other episodes