std :: shared_ptr Thread-Sicherheit erklärt

106

Ich lese http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html und einige Probleme mit der Thread-Sicherheit sind mir immer noch nicht klar:

  1. Standard garantiert, dass die Referenzzählung threadsicher und plattformunabhängig ist, oder?
  2. Ähnliches Problem - Standard garantiert, dass nur ein Thread (der die letzte Referenz enthält) delete für ein freigegebenes Objekt aufruft, oder?
  3. shared_ptr garantiert keine Thread-Sicherheit für darin gespeicherte Objekte?

BEARBEITEN:

Pseudocode:

// Thread I
shared_ptr<A> a (new A (1));

// Thread II
shared_ptr<A> b (a);

// Thread III
shared_ptr<A> c (a);

// Thread IV
shared_ptr<A> d (a);

d.reset (new A (10));

Durch Aufrufen von reset () in Thread IV wird die vorherige Instanz einer im ersten Thread erstellten Klasse gelöscht und durch eine neue Instanz ersetzt. Darüber hinaus sehen andere Threads nach dem Aufruf von reset () im IV-Thread nur neu erstellte Objekte?

Doof
quelle
24
Richtig, richtig und richtig.
Spraff
16
Sie sollten make_sharedanstelle vonnew
qdii

Antworten:

87

Wie andere bereits betont haben, haben Sie es in Bezug auf Ihre ursprünglichen 3 Fragen richtig herausgefunden.

Aber der letzte Teil Ihrer Bearbeitung

Durch Aufrufen von reset () in Thread IV wird die vorherige Instanz einer im ersten Thread erstellten Klasse gelöscht und durch eine neue Instanz ersetzt. Darüber hinaus sehen andere Threads nach dem Aufruf von reset () im IV-Thread nur neu erstellte Objekte?

ist falsch. Nur dzeigt auf das neue A(10), und a, bund czeigt weiterhin auf das Original A(1). Dies ist im folgenden kurzen Beispiel deutlich zu sehen.

#include <memory>
#include <iostream>
using namespace std;

struct A
{
  int a;
  A(int a) : a(a) {}
};

int main(int argc, char **argv)
{
  shared_ptr<A> a(new A(1));
  shared_ptr<A> b(a), c(a), d(a);

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;

  d.reset(new A(10));

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;
                                                                                                                 
  return 0;                                                                                                          
}

(Klar, ich habe mich nicht um Threading gekümmert: das berücksichtigt das shared_ptr::reset()Verhalten nicht.)

Die Ausgabe dieses Codes ist

a: 1 b: 1 c: 1 d: 1

a: 1 b: 1 c: 1 d: 10

Nicu Stiurca
quelle
35
  1. Richtig, shared_ptrs verwenden atomare Inkremente / Dekremente eines Referenzzählwerts.

  2. Der Standard garantiert, dass nur ein Thread den Löschoperator für ein freigegebenes Objekt aufruft. Ich bin nicht sicher, ob es speziell angibt, dass der letzte Thread, der seine Kopie des gemeinsam genutzten Zeigers löscht, derjenige ist, der delete aufruft (in der Praxis wäre dies wahrscheinlich der Fall).

  3. Nein, das darin gespeicherte Objekt kann von mehreren Threads gleichzeitig bearbeitet werden.

BEARBEITEN: Leichtes Follow-up. Wenn Sie sich ein Bild davon machen möchten, wie freigegebene Zeiger im Allgemeinen funktionieren, sollten Sie sich die boost::shared_ptrQuelle ansehen : http://www.boost.org/doc/libs/1_37_0/boost/shared_ptr.hpp .

Nichts mehr
quelle
3
1. Wenn Sie "'shared_ptrs'" sagen, verwenden Sie atomare Inkremente / Dekremente eines Referenzzählwerts. " Meinen Sie damit, dass sie keine interne Sperre für das atomare Inkrementieren / Dekrementieren verwenden, wodurch der Kontext wechselt? Könnten in einer einfachen Sprache mehrere Threads die Referenzanzahl erhöhen / verringern, ohne die Sperre zu verwenden? Und das atomare Inkrement wird durch spezielle Anweisungen für atomic_test_and_swap / atomic_test_and_increment durchgeführt?
rahul.deshmukhpatil
@rahul Der Compiler kann einen Mutex / Lock verwenden, aber die meisten guten Compiler verwenden keinen Mutex / Lock auf Plattformen, auf denen dies ohne Sperren möglich ist.
Bernard
@Bernard: Meinst du, es hängt von der Implementierung von "compilers std lib shared_ptr" für die Plattform ab?
rahul.deshmukhpatil
2
Ja. Nach meinem Verständnis besagt der Standard nicht, dass er sperrenfrei sein muss. Aber in den neuesten GCC und MSVC ist es auf Intel x86-Hardware sperrfrei, und ich denke, dass andere gute Compiler wahrscheinlich dasselbe tun, wenn die Hardware dies unterstützt.
Bernard
18

std::shared_ptr ist nicht threadsicher.

Ein gemeinsam genutzter Zeiger ist ein Paar von zwei Zeigern, einer auf das Objekt und einer auf einen Steuerblock (hält den Referenzzähler, verknüpft mit schwachen Zeigern ...).

Es kann mehrere std :: shared_ptr geben, und wenn sie auf den Steuerblock zugreifen, um den Referenzzähler zu ändern, ist er threadsicher, aber der std::shared_ptrselbst ist NICHT threadsicher oder atomar.

Wenn Sie ein neues Objekt einer std::shared_ptrZeit zuweisen, in der ein anderer Thread es verwendet, wird möglicherweise der neue Objektzeiger angezeigt, es wird jedoch weiterhin ein Zeiger auf den Steuerblock des alten Objekts verwendet => CRASH.

Lothar
quelle
4
Wir könnten sagen, dass eine einzelne std::shared_ptrInstanz nicht threadsicher ist. Von std :: shared_ptr Referenz:If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur;
JKovalsky
Dies könnte besser formuliert werden. Eine std::shared_ptr<T>Instanz ist garantiert threadsicher, wenn sie immer von einem Wert (kopiert / verschoben) über Threadgrenzen hinweg verwendet wird. Alle anderen Verwendungen std::shared_ptr<T>&sind über Thread-Grenzen hinweg unsicher
WhiZTiM