myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE7
   » Gadget für Vista
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» dotnetjob.de
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Gemeinschaft » .NET-Komponenten und C#-Snippets » Lockfreie threadsichere Queue
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Lockfreie threadsichere Queue

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Lockfreie threadsichere Queue

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Beschreibung:

Besonders einfache threadsichere Warteschlange, die ohne Locks auskommt:

C#-Code:
    public class LockFreeQueue<T>
    {
        public LockFreeQueue()
        {
            first = new Node();
            last = first;
        }

        Node first;
        Node last;

        public void Enqueue(T item)
        {
            Node newNode = new Node();
            newNode.item = item;
            Node old= Interlocked.Exchange(ref first, newNode);
            old.next = newNode;
        }

        public bool Dequeue(out T item)
        {
            Node current;
            do
            {
                current = last;
                if (current.next == null)
                {
                    item=default(T);
                    return false;
                }
            }
            while (current != Interlocked.CompareExchange(ref last, current.next, current));
            item = current.next.item;
            current.next.item = default(T);
            return true;
        }

        class Node
        {
            public T item;
            public Node next;
        }
    }

[Edit 12.8.2010]
1. Einen Vorschlag aufnenommen, im Enqueue Interlocked.Exchange anstatt einer Schleife mit CompareExchange zu verwenden -> performanter
2. Folgende Zeile ergänzt, damit nicht das zuletzt aus der Queue genommene Element noch referenziert wird: current.next.item = default(T);
3. Klasse public gemacht, die Klasse ist schließlich allgemein verwendbar.


Siehe auch:
 Lockfreier threadsicherer Stack
 Interlocked modify & threadsichere events

Lock free queue, non blocking, lockfree

Dieser Beitrag wurde 4 mal editiert, zum letzten Mal von Floste am 12.08.2010 16:41.

17.03.2009 12:32 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
zommi zommi ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2617.png


Dabei seit: 14.11.2007
Beiträge: 1.231
Entwicklungsumgebung: VS 2005+2010
Herkunft: Berlin


zommi ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi floste,

Gefällt mir sehr Daumen hoch

Was mir noch einfällt:
Die Ähnlichkeit zwischen Enqeue und Dequeue ist zwar einerseits sehr schön.
Aber folgendes sollte doch ebenfalls gehen (und damit die Einfachheit der besonders einfachen Queue weiter erhöhen): smile

C#-Code:
       public void Enqueue(T item)
        {
            Node newNode = new Node();
            newNode.item = item;
            Node old = Interlocked.Exchange(ref first, newNode);
            old.next = newNode;
        }

beste Grüße
zommi
17.03.2009 13:07 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
kleines_eichhoernchen kleines_eichhoernchen ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2079.jpg


Dabei seit: 07.11.2006
Beiträge: 3.971
Entwicklungsumgebung: Visual Studio 2005 (C#)
Herkunft: Ursprünglich Vogtland, jetzt Much


kleines_eichhoernchen ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo floste,
was gefällt dir an lock und LinkList<T> nicht?
17.03.2009 13:48 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
zommi zommi ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2617.png


Dabei seit: 14.11.2007
Beiträge: 1.231
Entwicklungsumgebung: VS 2005+2010
Herkunft: Berlin


zommi ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi nochmal.

Prinzipiell sollten die Interlocked-Konstrukte performanter sein. Weil es (bei passender CPU-Unterstützung) direkt Befehle dafür gibt. Zudem bedeutet ein lock auch immer etwas zusätzlichen Overhead. (Und gerade bei Collections sollte so ein Overhead immer klein gehalten werden)

Zudem stellt sich bei locks immer die Frage nach privaten oder offentlichen. Und zu guter letzt muss man auch noch auf Deadlocks achten.

All das entfällt bei obiger Variante. Wenn man also mal eine LinkedList selbst implementieren muss und wegen den genannten Gründen auf locks gerne verzichten möchte, ist das Snippet oben sehr schön.

beste Grüße
zommi
17.03.2009 14:01 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.571
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo floste,

[EDIT]Der folgende Einwand ist unberechtigt, wie sich weiter unten zeigt.[/EDIT]

ich glaube nicht, dass die Implementierung sicher ist. Nachdem im Enqueue first per Interlocked.CompareExchange umgesetzt wurde und bevor old.next = newNode; ausgeführt wird, ist die Queue in einem inkonsistenten Zustand, der beliebig lange andauern kann. Im Prinzip können von verschiedenen Threads sogar mehrere Enqueues hintereinander ausgeführt werden, die alle zufällig vor dem old.next = newNode; unterbrochen werden. Spätestens dann fallen aber mehrere Dequeues hintereinander auf die Nase. Ich sehe leider auch erstmal nicht, wie sich das Problem lösen lässt.

herbivore
17.03.2009 17:44 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Wenn der Thread zu diesem Zeitpunkt plötzlich wartet, sind neue Elemente solange unzugänglich, bis der Thread fortfährt. (Falls er nie fortfahren sollte für immer.) Wenn zu diesem Zeitpunkt weitere Threads Enqueue aufrufen, werden die Elemente zwar angefügt, sind aber durch die Unterbrechung unzugänglich. Sobald der Thread fortfährt, sind alle Elemente wieder zugänglich und es sind keine verloren gegangen.

Das gleiche Problem hat man aber auch bei Locks, da die Standardqueue auch einen inkonsistenten Zustand hat, während Enquene aufgerufen wird. Augenzwinkern

Wenn man also nach dir geht, kann man auch mit Locks nichts Threadsicher machen ( Applikation mit Warteschlange)

Dieser Beitrag wurde 4 mal editiert, zum letzten Mal von Floste am 17.03.2009 19:17.

17.03.2009 18:54 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.571
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo floste,

Zitat:
Wenn man also nach dir geht, kann man auch mit Locks nichts Threadsicher machen

doch, das lock ist ja gerade dafür, dass während eines inkonsistenten Zustands kein anderer zugreifen kann. Gerade dadurch wird es mit lock thread-sicher.

Zitat:
Wenn der Thread zu diesem Zeitpunkt plötzlich wartet, sind neue Elemente solange unzugänglich, bis der Thread fortfährt.

Ja, du hast Recht und ich nehme meine anders lautende Vermutung zurück. Und um das auch noch einzugestehen: Eine Queue mit lock wäre in dem geschilderten Fall des lange unterbrochenen Threads deiner Queue unterlegen, weil solange überhaupt keine Operation auf der Queue möglich wären. Bei deiner wären immer noch weitere Enqueues möglich.

Zitat:
public bool Dequeue(ref T item)

Einen habe ich trotzdem noch. :-) Du solltest ref in out ändern.


Hallo zusammen,

Zitat:
 Applikation mit Warteschlange

der Unterschied zu der Queue von floste ist, dass bei meiner Queue ein Aufruf von Dequeue absichtlich solange blockiert, bis ein neues Element verfügbar ist. Damit vermeidet man inbesondere beim Einsatz als Job-Queue das Busy-Waiting auf neue Elemente (Jobs).

herbivore
17.03.2009 19:48 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von herbivore:
doch, das lock ist ja gerade dafür, dass während eines inkonsistenten Zustands kein anderer zugreifen kann. Gerade dadurch wird es mit lock thread-sicher.

Du hast es aber einen inkonsistenten Zustand genannt, dass man neuere Elemente solange nicht abrufen kann, bis der Thread fortfährt. (So habe ich es verstanden.)
Bei lock wäre nichtnur das der Fall sondern es würden auchnoch alle zugreifenden Threads blockiert.
Also: Entweder kann man mit locks nichts threadsicher machen,da inkonsistente Zusände auftreten, oder ich habe dich falsch verstanden es treten bei mir keine inkonsistenten Zustände auf.

Zitat:
Einen habe ich trotzdem noch. :-) Du solltest ref in out ändern.

Das habe ich nicht gemacht, da nicht in jedem Fall der Wert verändert wird, da ja nicht in jedem Fall etwas abgerufen werden kann.
17.03.2009 20:21 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.571
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo floste,

du hast mich falsch verstanden. Aber das müssen wir auch hier unter Projekte nicht ausdiskutieren, zumal ich denke, dass inbesondere mein letzter Beitrag klar genug war. Da habe ich ja auch schon eingestanden, dass lock in dem speziellen Fall die Threads blockieren würde.

Zitat:
Das habe ich nicht gemacht, da nicht in jedem Fall der Wert verändert wird, da ja nicht in jedem Fall etwas abgerufen werden kann.

Das ist bei TryParse genauso und trotzdem wird out verwendet. Du kannst und solltest, in dem Fall, dass kein Wert zurückgegeben werden kann, default (T) verwenden.

herbivore
17.03.2009 21:32 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
0815Coder
myCSharp.de-Mitglied

images/avatars/avatar-242.gif


Dabei seit: 08.12.2005
Beiträge: 767


0815Coder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

abgesehen von diesen Problemen...

das hier:

C#-Code:
while (old != Interlocked.CompareExchange(ref first, newNode, old));

ist doch Busywaiting mit Prozessorlast auf 100%... (Es sei denn dass das CompareExchange direkt selber wartet)... jedenfalls ist es Polling und das sollte man auch vermeiden.
19.03.2009 18:56 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
ist doch Busywaiting mit Prozessorlast auf 100%... (Es sei denn dass das CompareExchange direkt selber wartet)... jedenfalls ist es Polling und das sollte man auch vermeiden.

Ja, busy schon aber nicht wie üblich, dass er darauf wartet, dass eine Veränderung eintritt, sondern darauf, dass keine Veränderung eintritt, was wohl bedeutend häufiger der Fall ist.
Er prüft, ob der wert geändert wurde und ersetzt wenn nicht den Wert. Ansonsten wird die Prozedur wiederholt. Dass der Wert geändert wurde, heißt aber, dass ein anderer Thread den Wert geändet hat und dadurch aufhört zu warten.
Das heißt durch einen Aufruf von Enqueue/Dequeue eines Threads müssen alle anderen Threads maximal einen Schleifendurchlauf warten.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Floste am 10.04.2009 14:49.

19.03.2009 19:24 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Jahr.
Spook Spook ist männlich
myCSharp.de-Mitglied

Dabei seit: 28.10.2008
Beiträge: 119
Entwicklungsumgebung: VS2010 Prem
Herkunft: Esslingen a.N.


Spook ist offline Füge Spook Deiner Kontaktliste hinzu

Bugfix: Referenz-Leak

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

man sollte Dequeue noch so erweitern, dass vor dem Verlassen der Methode das zurückgegebene Element aus der Datenstruktur entfernt wird:

C#-Code:
    ...
    item = current.next.item;
    current.next.item = default(T);
    return true;
}

Bei Dequeue wird die Verkettete Liste um eine Element verschoben (effektiv: last wird zu last.next). Dabei wird diese Node zum neuen "Anker" der Liste und wird NICHT vom GC abgeräumt - auch nicht das darin enthaltene Element.

Grüße

spooky
12.08.2010 14:39 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland

Themenstarter Thema begonnen von Floste

Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ein Element, das etwas später freigegeben wird, verbraucht in den meisten Fällen nicht soo viel Speicher, aber da dies offensichtlich einigen Leuten wichtig ist, habe ich deine Zeile ergänzt.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Floste am 12.08.2010 16:46.

12.08.2010 16:45 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 4 Jahre.
Der letzte Beitrag ist älter als 2 Jahre.
Antwort erstellen


© Copyright 2003-2013 myCSharp.de-Team. Alle Rechte vorbehalten. 20.06.2013 12:32