2008-09-18 13 views
19

Wie implementieren Sie ein effizientes und threadsicheres Referenzzählsystem auf X86-CPUs in der Programmiersprache C++?Wie man threadsichere Referenzzählung in C++ implementiert

Ich laufe immer auf das Problem, dass die kritischen Operationen nicht atomaren, und die verfügbaren X86 Interlock-Operationen sind nicht ausreichend für die Implementierung der Ref-Zählsystem.

Der folgende Artikel behandelt das Thema, sondern erfordert eine spezielle CPU-Anweisungen:

http://www.ddj.com/architect/184401888

Antwort

11

Heutzutage kann man die Anhebung/TR1 Shared_ptr <> Smart-Pointer zu halten Ihre Referenz gezählt Referenzen verwenden.

Funktioniert gut; kein Aufhebens, kein Muss. Die Klasse shared_ptr <> kümmert sich um alle Sperren, die auf dem Refcount benötigt werden.

+0

Auch Beachten Sie, dass es sich bei Technical Report 1 (TR1) um einen Entwurf für Artikel handelt, die in den C++ 0x-Standard aufgenommen werden sollen. Wenn C++ 0x näher kommt, werden diese sogar noch besser unterstützt (sie basieren auf der Boost-Bibliothek). –

+0

Ich habe meine Zweifel an der Lock-Free-Implementierung, aber das ist wahrscheinlich immer noch die beste Wahl. Multi-Thread ist in C++ mit allen Tricks, die der Compiler an Ihrem Code machen kann, teuflisch schwer zu machen. –

+9

Man ... Entweder fehlt mir etwas, oder irgendetwas stimmt nicht mit dieser Antwort ... Warum wurde es so oft akzeptiert und neu gewählt, wenn es nicht einmal die ursprüngliche Frage beantwortet - es deutet nur etwas an Sonst hat das bereits eine funktionierende Implementierung der gleichen Sache, die in OQ war? – Paulius

3

Genau genommen müssen Sie warten, bis C++ 0x thread-sicheren Code in reinem C++ schreiben kann.

Jetzt können Sie Posix verwenden oder Ihre eigenen plattformunabhängigen Wrapper zum Vergleichen und Austauschen und/oder verriegelten Inkrementieren/Dekrementieren erstellen.

+1

Oder tun Sie wie alle anderen und verwenden Boost :: TR1 – gnud

+0

Wenn Sie sich nicht um Win32 kümmern, ist Posix die beste Wahl (schnell und einfach), IMO. Heutzutage unterstützen Hauptcompiler C++ 0x, was bedeutet, dass Sie TR1 (das Teil des C++ 11-Standards ist) verwenden können. Sie müssen nur die Option aktivieren (C++ 0x Compiler-Flag). – Pijusn

4

In VC++ können Sie _InterlockedCompareExchange verwenden.

do 
    read the count 
    perform mathematical operation 
    interlockedcompareexchange(destination, updated count, old count) 
until the interlockedcompareexchange returns the success code. 

Auf anderen Plattformen/Compiler mit dem entsprechenden intrinsischen für die LOCK cmpxchg Anweisung, dass die MS der _InterlockedCompareExchange aussetzt.

+1

Oder einfach InterlockedIncrement/Decrement. – yrp

+0

@yrp: ist es schneller als eine do while loop/vergleichen und tauschen? AFAIK, IntelockedIncrement/Decement geben LOCK INC/LOCK DEC x86-Anweisungen aus, während cas nicht (zumindest LOCK ist für diese Anweisung implizit). – Stringer

0

Wenn die Anweisung selbst nicht atomar ist, müssen Sie den Abschnitt des Codes, der die entsprechende Variable aktualisiert, zu einem kritischen Abschnitt machen.

d. H. Sie müssen verhindern, dass andere Threads diesen Codeabschnitt mithilfe eines Sperrschemas eingeben. Natürlich müssen die Sperren atomar sein, aber Sie können einen atomaren Sperrmechanismus innerhalb der pthread_mutex-Klasse finden.

Die Frage nach effizienten: Die Pthread-Bibliothek ist so effizient wie es sein kann und immer noch garantieren, dass Mutex-Sperre atomare für Ihr Betriebssystem ist.

Ist es teuer: Wahrscheinlich. Aber für alles, was eine Garantie erfordert, entstehen Kosten.

0

Dieser spezielle Code, der in diesem ddj-Artikel gepostet wird, fügt zusätzliche Komplexität hinzu, um Bugs bei der Verwendung von Smartpointern Rechnung zu tragen.

Insbesondere, wenn Sie nicht garantieren können, dass sich der Smart Pointer bei einer Zuweisung zu einem anderen Smart Pointer nicht ändert, tun Sie es falsch oder machen etwas sehr unzuverlässiges, um damit zu beginnen. Wenn sich der intelligente Zeiger ändern kann, während er einem anderen intelligenten Zeiger zugewiesen wird, bedeutet dies, dass der Code, der die Zuweisung ausführt, nicht den intelligenten Zeiger besitzt, der zunächst verdächtig ist.

2

Win32 InterlockedIncrementAcquire und InterlockedDecrementRelease (wenn Sie sicher und vorsichtig sein wollen über Plattformen mit möglicher Neuordnung, daher müssen Sie Speicherbarrieren zur gleichen Zeit ausgeben) oder InterlockedIncrement und InterlockedDecrement (wenn Sie sicher sind, dass x86 bleiben), sind atomar und werden den Job machen.

Das heißt, Boost/TR1 shared_ptr <> wird all dies für Sie behandeln, daher, wenn Sie es nicht auf eigene Faust implementieren müssen, werden Sie wahrscheinlich das Beste tun, um dabei zu bleiben.

1

Bedenken Sie, dass das Sperren sehr teuer ist, und jedes Mal, wenn Sie Objekte zwischen intelligenten Zeigern übergeben - auch wenn das Objekt derzeit einem Thread gehört (die intelligente Zeigerbibliothek weiß das nicht).

dies gegeben, kann es eine Faustregel gilt: anwendbar hier (ich bin glücklich korrigiert werden!)

Wenn die Folge Dinge auf Sie zutreffen:

  • Sie komplexe Datenstrukturen haben, die wäre schwer Destruktoren für schreiben (oder wo STL-Stil Wert Semantik wäre unpassend, von Entwurf), so dass Sie Smart-Zeigern, es für Sie zu tun, und
  • Sie verwenden mehrere Threads, die diese Objekte teilen, und
  • Sie interessieren sich für per Formance sowie Korrektheit

... dann kann die tatsächliche Garbage Collection eine bessere Wahl sein. Obwohl GC einen schlechten Ruf für die Leistung hat, ist alles relativ. Ich glaube, dass es sehr gut mit dem Sperren von Smartpointern verglichen wird. Es war ein wichtiger Teil, warum das CLR-Team sich für echte GC entschieden hat und nicht für eine Referenzzählung. Siehe this article, insbesondere diese krassen Vergleich dessen, was Referenzzuordnung bedeutet, wenn Sie zählen los:

keine ref Zählung:

a = b; 

ref Zählung:

if (a != null) 
    if (InterlockedDecrement(ref a.m_ref) == 0) 
      a.FinalRelease(); 

if (b != null) 
    InterlockedIncrement(ref b.m_ref); 

a = b; 
+0

Was passiert, wenn ein anderer Thread zwischen 'InterlockedDecrement (...)' und 'a.FinalRelease();'? – smoku

+0

Es ist unmöglich im Design - das wurde Code generiert, nicht handgeschrieben. Der Anwendungscode sagt nur "a = b". Wenn zwei Threads Zugriff auf das Objekt haben, die ausreichen, um die Zählung zu inkrementieren, dann ist die Zählung bereits> = 2. Entweder kann sie dekrementieren, aber dabei gibt sie gleichzeitig die Fähigkeit auf, sie zu inkrementieren. –

+0

Was passiert, wenn ein anderer Thread 'a = b' zwischen 'InterlockedDecrement (...) 'und' a.FinalRelease(); '? – smoku

Verwandte Themen