Grenzen für sperrfreie Sammlungen?

10

David Rodríguez - dribeas schrieb in einem Kommentar zu StackOverflow, dass "nicht alle Sammlungen ohne Sperren implementiert werden können". Ich bin mir nicht sicher, ob dies wahr ist, und ich kann so oder so keinen Beweis finden.

Diese Aussage ist nicht sehr präzise, ​​aber lassen Sie mich versuchen, sie etwas formeller umzuformulieren: Für jeden Sammlungstyp gibt es einen sperrenfreien Sammlungstyp LF , der die gleichen Operationen bietet, und für jede Operation in LF hat die gleiche Big-O-Komplexität wie die entsprechende Operation an .CCCC

Ich erwarte übrigens keine Transformation.

MSalters
quelle
1
Als Nicht-Experte frage ich mich, ob „sperrenfrei“ genau definiert werden kann.
Tsuyoshi Ito
1
@Suresh: Vielleicht ein Synonym für "Datenstruktur"?
Tsuyoshi Ito
2
Was ist, wenn Sie nur eine sperrfreie Implementierung von STM (Software Transactional Memory) durchführen und darüber hinaus Datenstrukturen implementieren?
Jukka Suomela
5
@ Tsuyoshi: Ich denke, es gibt keine formale Definition von lock-free. Informell bedeutet dies, dass Sie den langsamen LOCK-Befehl der CPU nicht verwenden und sich an den schnelleren Vergleich und Austausch halten. Da ein LOCK mit Compare-and-Swap simuliert werden kann, ist es schwierig, eine harte Grenze zwischen "Sie verwenden hier im Wesentlichen Compare-and-Swap, um eine Sperre (oder eine Transaktion für diese Angelegenheit) zu simulieren" und "Oh, das ist a wirklich kluger Einsatz von Vergleichen und Tauschen, und es sieht überhaupt nicht so aus, als ob es eine Operation auf höherer Ebene simuliert, von der wir wissen. "
Radu GRIGore
1
Nach meinem Verständnis bedeutet "sperrenfrei" hier ein Synonym für "nicht blockierend". Dies beinhaltet nicht die LOCKAnweisung der CPU, sondern den Thread-Scheduler über Mutexe / Semaphoren / etc.
MSalters

Antworten:

11

Da ich selbst etwas verwirrt war, beginne ich mit der Klärung einiger Konzepte in der Frage.

Sammlung . Ich sehe keinen Grund, Zeit damit zu verbringen, genau zu definieren, was "Sammlung" bedeutet, wenn wir einfach fragen können, was für Datenstrukturen im Allgemeinen passiert. Eine Datenstruktur belegt einen Speicherplatz und verfügt über einige Operationen , die auf diesen Speicher zugreifen und von Benutzern aufgerufen werden können . Diese Benutzer können unterschiedliche Prozessoren oder nur unterschiedliche Threads sein, es geht uns nichts an. Alles was zählt ist, dass sie Operationen parallel ausführen können.

Schlossfrei . Herlihy und Boss sagen, dass eine Datenstruktur sperrfrei ist, wenn ein abstürzender Benutzer die weitere Verwendung der Datenstruktur nicht verhindert. Stellen Sie sich zum Beispiel vor, man gießt Wasser auf einen Prozessor, der gerade einen Knoten in einen sortierten Satz einfügt. Wenn andere Prozessoren später versuchen, in diese sortierte Gruppe einzufügen, sollten sie erfolgreich sein. ( Edit: Nach dieser Definition ist es der Fall , dass , wenn eine Datenstruktur , Einsatzschlösser dann ist es nicht sperren frei, aber es ist nicht der Fall , dass , wenn eine Datenstruktur Sperren nicht verwenden , dann ist es Lock-frei.)

Mit dieser Definition sagen Herlihy und Boss im Grunde, dass die Antwort darin besteht, kritische Regionen in Transaktionen umzuwandeln.

Aber, fragen Sie sich vielleicht, hat dies die gleiche Komplexität? Ich bin mir nicht sicher, ob die Frage Sinn macht. Überlegen Sie push(x) { lock(); stack[size++] = x; unlock(); }. Ist das eine konstante Zeitoperation? Wenn Sie den Sperrvorgang und damit andere Benutzer ignorieren, können Sie mit JA antworten. Wenn Sie andere Benutzer nicht ignorieren möchten, können Sie nicht sagen, ob der Push in konstanter Zeit ausgeführt wird. Wenn Sie eine Ebene höher gehen und sehen, wie der Stapel von einem bestimmten Algorithmus verwendet wird, können Sie möglicherweise sagen, dass der Push immer eine konstante Zeit in Anspruch nimmt (gemessen jetzt an der Eingabe Ihres parallelen Algorithmus). Aber das ist wirklich eine Eigenschaft Ihres Algorithmus, daher ist es nicht sinnvoll zu sagen, dass Push eine konstante Zeitoperation ist .

Wenn Sie ignorieren, wie lange ein Benutzer, der eine Operation ausführt, auf andere Benutzer wartet, beantwortet die Verwendung von Transaktionen anstelle kritischer Bereiche Ihre Frage positiv. Wenn Sie die Wartezeit nicht ignorieren, müssen Sie sich ansehen, wie die Datenstruktur verwendet wird.

Radu GRIGore
quelle
Ich bin mir nicht sicher, ob Sie tatsächlich davon ausgehen können, dass die pushoben angegebene Operation keine Operation mit konstanter Zeit ist. Für eine feste Anzahl von Prozessoren, und eine gemeinsame Implementierung lockdavon garantiert keinen Hunger, nimmt die obige Operation (im schlimmsten Fall für einen gegebenen Prozessor N_proc * O (1), was naiv als O (1) angenommen werden kann ( Anzahl der Prozessoren, die in die verborgene Konstante
einbezogen werden
nf(n)f
Nun, Speicherzugriff ist ein häufiger Fall davon. Die meisten Algorithmusanalysen gehen davon aus, dass der Speicherzugriff unabhängig vom verwendeten Speicher O (1) ist. Reale Speicherarchitekturen (mit Caches usw.) werden durch O (log N) besser angenähert, wobei N der verwendete Speicher ist.
MSalters
Die Annahme, dass die Anzahl der Prozessoren eine Konstante ist, ist zwar recht praktisch, aber ich werde sie vermeiden. Dann besteht das Problem darin, dass die Komplexität nicht eindimensional analysiert werden kann, da die Größe des Problems sowohl in der Größe der Eingabe als auch in der Anzahl der Prozessoren, die beide orthogonale Dimensionen sind, zwangsläufig zunimmt. Unter der Annahme eines bestimmten Containers in der C ++ - Standardbibliothek (ich wähle offensichtlich einen harten aus) besteht eine der Anforderungen darin, dass alle Elemente in einem zusammenhängenden Speicherblock gespeichert werden.
David Rodríguez - Dribeas
Das Anhängen eines Elements an den Vektor ist nun eine amortisierte Operation mit konstanter Zeit (wenn es nicht in den zuvor zugewiesenen Block passt, dauert der Aufruf eine lineare Zeit für die Anzahl der Elemente im Container, aber wenn der reservierte Speicherblock ist nach einer exponentiellen Abfolge erworben, sind die fortgeführten Anschaffungskosten konstant). Wenn Sie einen thread-sicheren Container implementieren, würden Sie die Änderung sperren und dann durchführen. Die Kosten der Operation sind proportional zu den Kosten für das Sperren - was ich nicht wirklich weiß ... aber in erster Näherung können Sie meistens berücksichtigen Konstante
David Rodríguez - Dribeas
3

Ich denke, dass "COLLECTIONS" für "Warteschlangen, Stapel, verknüpfte Listen, Bäume, ..." steht.

Von http://www.cl.cam.ac.uk/research/srg/netos/lock-free/

Durch sorgfältiges Design und Implementierung können Datenstrukturen erstellt werden, die für die gleichzeitige Verwendung sicher sind, ohne dass Sperren verwaltet oder Threads blockiert werden müssen. Diese nicht blockierenden Datenstrukturen können die Leistung steigern, indem sie zusätzliche Parallelität ermöglichen, und die Robustheit verbessern, indem einige der Probleme vermieden werden, die durch Prioritätsumkehr in lokalen Einstellungen oder Maschinen- und Verbindungsfehler in verteilten Systemen verursacht werden.

Die beste allgemeine Einführung in unsere nicht blockierenden Algorithmen ist das derzeit eingereichte Papier Concurrent Programming without Locks, das unsere Entwürfe für das Vergleichen und Austauschen von mehreren Wörtern, den wortbasierten Software-Transaktionsspeicher und den objektbasierten Software-Transaktionsspeicher abdeckt.

Wenn "sperrenfrei" bedeutet "Semaphores, Mutex, Monitore usw. des Betriebssystems nicht verwenden ...", dann denke ich (aber ich bin kein Experte), dass jede Sammlung mit atomarem Lese- / Schreibzugriff sperrenfrei gemacht werden kann. Ändern Sie Grundelemente, die von der Hardware unterstützt werden müssen.

O()

Eine ausführliche Dokumentation zu diesem Thema finden Sie online:

http://www.google.it/search?q=lock+free+algorithm+filetype%3Apdf

(... und weitere Verweise am Ende jedes Dokuments)

Marzio De Biasi
quelle