Frage

Weiß jemand, der eine vollständig Thread-sichere shared_ptr Implementierung? Z.B. Boost-Implementierung von shared_ptr ist Thread-sicher für die Ziele (Refcounting) und auch sicher für die gleichzeitige shared_ptr Instanz liest, aber nicht schreibt oder zum Lesen / Schreiben.

(siehe Boost-docs , Beispiele 3, 4 und 5).

Gibt es eine shared_ptr-Implementierung, die vollständig Thread-sicher für shared_ptr Instanzen ist?

Seltsam, die docs steigern sagen, dass:

  

Shared_ptr Objekte bieten das gleiche Niveau der Thread-Sicherheit als integrierte Typen.

Aber wenn Sie einen gewöhnlichen Zeiger (integrierte Typ) zu vergleichen, um smart_ptr, dann gleichzeitiges Schreiben eines gewöhnlichen Zeiger ist Thread-sicher, aber gleichzeitiges Schreiben auf einen smart_ptr nicht.

EDIT:. Ich meine eine Lock-freie Implementierung auf x86-Architektur

EDIT2: Ein Beispiel Anwendungsfall für einen solchen Smart-Pointer wäre, wo es eine Reihe von Arbeitsthreads, welches Update eine globale shared_ptr mit ihrer aktuellen Arbeitseinheit und einen Monitor-Thread sind die Stichproben der Arbeitsaufgaben erfolgt. Der Shared-ptr würde besitzen die Arbeitseinheit, bis ein anderes Arbeitselement Zeiger darauf zugeordnet ist (um dadurch die vorherige Arbeitselement zu zerstören). Der Monitor würde das Eigentum an der Workitem erhalten (wodurch das Arbeitselement verhindert werden zerstört), indem sie ihre eigenen Shared-ptr zuweisen. Es kann mit XCHG und manuellen Löschen getan werden, aber wäre schön, wenn ein Shared-ptr es tun könnte.

Ein weiteres Beispiel ist, wo der globale Shared-ptr einen „Prozessor“ hält, und wird von einem Thread zugeordnet ist, und von einem anderen Thread verwendet. Wenn der „Benutzer“ thread sieht, dass der Prozessor Scherbe-ptr NULL ist, verwendet es eine alternative Logik der Verarbeitung zu tun. Wenn es nicht NULL ist, verhindert, dass der Prozessor von durch sie ihre eigenen Shared-ptr Zuweisung zerstört zu werden.

War es hilfreich?

Lösung

die erforderlichen Barrieren für eine solche vollständig Thread-sichere Shared_ptr Implementierung würde wahrscheinlich die Leistung auswirken Hinzufügen. Betrachten Sie das folgende Rennen (Anmerkung: Pseudo-Code ist reich):

Gewinde 1:     global_ptr = A;

Thread 2:     global_ptr = B;

Thema 3:     local_ptr = global_ptr;

Wenn wir das brechen in seine konstituierenden Operationen:

Gewinde 1:

A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Thread 2:

B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Thema 3:

local_ptr = global_ptr;
local_ptr.refcnt++;

Klar, wenn Gewinde 3 den Zeiger nach A des Swaps liest, dann B geht und löscht sie vor dem Referenzzähler erhöht werden kann, wird schlechte Dinge passieren.

Um dies zu umgehen, müssen wir einen Dummy-Wert verwendet werden, während Faden 3 die REFCNT Update tun: (Anmerkung: compare_exchange (Variable, erwartet, neu) ersetzt atomar den Wert in den Variablen mit neuem, wenn es um neue Zeit gleich ist, dann true zurück, wenn es so erfolgreich ist)

Gewinde 1:

A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Thread 2:

B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Thema 3:

tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
    tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;

Sie haben hatte nun eine Schleife hinzufügen, mit atomics in es in der Mitte des / lesen / Betrieb. Dies ist nicht eine gute Sache - es kann bei einigen CPUs extrem teuer sein. Was mehr ist, sind Sie damit beschäftigt Warte auch. Sie können mit futexes und so weiter zu bekommen klug beginnen -. Aber von diesem Punkt Sie die Sperre neu erfunden haben

Diese Kosten, die von jedem Betrieb getragen werden muss, und ist sehr ähnlich in der Natur, was eine Sperre würden Sie auf jeden Fall geben, ist, warum Sie in der Regel nicht so thread-safe Shared_ptr Implementierungen sehen Sie. Wenn Sie so etwas brauchen, würde ich empfehlen, einen Mutex und shared_ptr in einem Convenience-Klasse Einwickeln Verriegelung zu automatisieren.

Andere Tipps

Die gleichzeitige Schreiben auf einen eingebauten in Zeiger fädelt sicher nicht sicher. Betrachten wir die Auswirkungen in Bezug auf Speicherbarrieren auf den gleichen Wert zu schreiben, wenn Sie wirklich fahren wollen, sich verrückt (zum Beispiel könnten Sie zwei Threads haben die gleiche Zeiger unterschiedliche Werte hatten zu denken).

RE: Kommentar - der Grund Einbauten nicht doppelt Löschen sind, weil sie überhaupt nicht zu löschen ist (und die Umsetzung von boost :: shared_ptr ich würde löscht nicht verdoppeln, da es einen speziellen Atom-Schritt verwendet und Abnahme, so wäre es nur einzelne löschen, aber dann wäre das Ergebnis könnte den Zeiger von einem haben und den Referee Graf von der anderen Seite. Oder so ziemlich jede Kombination der beiden. es wäre schlecht.). Die Aussage in dem Boost-docs ist richtig, wie es ist, können Sie die gleichen Garantien erhalten, wie Sie mit einem eingebauten in tun.

RE: EDIT2 - Die erste Situation, die Sie beschreiben, sind sehr unterschiedlich zwischen Einbauten und shared_ptrs verwenden. In einer (XCHG und manuell löschen) gibt es keine Referenzzahl; Sie gehen davon aus Sie der einzige Eigentümer sind, wenn Sie dies tun. Wenn gemeinsamen Zeiger verwenden, sagen Sie anderen Threads Besitz haben könnte, die Dinge viel komplizierter macht. Ich glaube, es ist möglich, mit einem Vergleichs- und Swap, aber dies wäre sehr nicht tragbar.

C ++ 0x kommt mit einer atomics Bibliothek aus, die es viel einfacher zu schreiben generischen multi-threaded Code machen sollte. Sie werden wahrscheinlich warten müssen, bis das herauskommt gute Cross-Plattform-Referenzimplementierungen von Thread-sichere Smart-Pointer zu sehen.

Ich weiß nicht, von einem solchen Smart-Pointer-Implementierung, auch wenn ich fragen: wie dieses Verhalten nützlich sein könnte? Die einzigen Szenarien ich denken kann, wo man die gleichzeitige Zeiger-Updates finden würden, sind Rennbedingungen (das heißt Bugs).

Dies ist keine Kritik - es gibt auch einen legitimen Fall sein kann, kann ich einfach nicht daran denken. Bitte lassen Sie mich wissen!

Re: EDIT2 Vielen Dank für ein paar Szenarien bieten. Es klingt wie Atom-Zeiger schreibt in solchen Situationen nützlich sein würde. (Ein kleines Ding: für das zweite Beispiel, wenn Sie schrieb: „Wenn es nicht NULL ist, es verhindert, dass der Prozessor von durch sie ihre eigenen Shared-ptr Zuweisung zerstört“, ich hoffe, Sie bedeuten, dass Sie die globalen gemeinsamen Zeiger auf die zuweisen lokaler gemeinsamer Zeiger erster und prüfen, ob der lokale gemeinsame Zeiger NULL ist - so, wie Sie es beschrieben ist anfällig für eine race-Bedingung, wo der globale Shared-Zeiger NULL wird, nachdem Sie es testen und bevor Sie es zuweisen auf die lokale ein.)

Sie können diese Implementierung verwenden Atomic Referenz Pointers Counting zumindest zu implementieren die Referenzzählung Mechanismus.

Ihr Compiler kann bereits bietet den Faden sicher intelligente Zeiger in den neueren C ++ Standards. Ich glaube, TBB auf das Hinzufügen eines Smart-Pointer plant, aber ich glaube es noch nicht aufgenommen worden ist. Sie können in der Lage sein, einen von TBB Thread sicheren Behälter zu verwenden, though.

Sie können dies leicht tun, indem ein Mutex-Objekt mit jedem gemeinsamen Zeiger einschließlich und Einwickeln Inkrement / Dekrement-Befehlen mit dem Schloss.

Ich denke nicht, das so einfach ist es nicht genug, um Ihre sh_ptr Klassen mit CS zu wickeln. Es ist wahr, dass, wenn Sie ein einziges CS für alle freigegebenen Zeiger beibehalten kann es gegenseitigen den Zugriff und Löschung von sh_ptr Objekten unter verschiedenen Threads zu vermeiden gewährleisten. Aber das wäre schrecklich, würde ein CS-Objekt für jeden freigegebenen Zeiger ein echter Engpass sein. Es wäre geeignet, wenn alle wickelbar neue ptr -s haben unterschiedliche CS s'aber auf diese Weise wir unsere CS dinamically schaffen sollten, und die Kopie ctors von sh_ptr Klassen gewährleisten diese gemeinsame Cs zu übertragen. Nun kamen wir auf das gleiche Problem: die quaranties, dass diese Cs ptr bereits gelöscht ist oder nicht. Wir können ein wenig mehr Smarty mit flüchtigen m_bReleased Flags pro Instanz sein, aber so können wir nicht die Sicherheitslücken zwischen der Flagge überprüft und mit dem gemeinsamen Cs stecken können. Ich kann nicht ganz sicher Lösung für dieses Problem sehen. Vielleicht wäre das schreckliche globale Cs die kleineren schlecht, wie die App zu töten. (Sorry für mein Englisch)

Das ist vielleicht nicht genau, was Sie wollen, aber die boost::atomic Dokumentation stellt ein Beispiel dafür, wie ein Atomzähler mit intrusive_ptr zu verwenden. intrusive_ptr ist eine der Boost-Smart Pointer, tut es „intrusive Referenzzählung“, was bedeutet, wird der Zähler „eingebettet“ in dem Ziel stattdessen durch den Smartpointer zu liefern.

Boost-atomic Anwendungsbeispiele:

http://www.boost.org/doc/html/atomic/ usage_examples.html

Meiner Meinung nach ist die einfachste Lösung ist es, eine intrusive_ptr mit einem paar kleineren zu verwenden (aber notwendig) Änderungen.

Ich teilte meine Implementierung unter:

http://www.philten.com/boost-smartptr-mt/

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top