Wyjaśnienie bezpieczeństwa wątków std :: shared_ptr

106

Czytam http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html i niektóre problemy z bezpieczeństwem wątków nadal nie są dla mnie jasne:

  1. Standardowo gwarantuje, że zliczanie referencji jest obsługiwane wątkowo i niezależne od platformy, prawda?
  2. Podobny problem - standardowa gwarancja, że ​​tylko jeden wątek (trzymający ostatnią referencję) wywoła delete na udostępnionym obiekcie, prawda?
  3. shared_ptr nie gwarantuje żadnego bezpieczeństwa wątków dla przechowywanych w nim obiektów?

EDYTOWAĆ:

Pseudo kod:

// 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));

Wywołanie reset () w wątku IV spowoduje usunięcie poprzedniej instancji klasy A utworzonej w pierwszym wątku i zastąpienie jej nową instancją? Ponadto po wywołaniu reset () w wątku IV inne wątki będą widzieć tylko nowo utworzony obiekt?

Głupkowaty
źródło
24
Racja, racja i racja.
spraff
16
powinieneś użyć make_sharedzamiastnew
qdii

Odpowiedzi:

87

Jak zauważyli inni, udało Ci się poprawnie zorientować się w trzech pierwotnych pytaniach.

Ale ostatnia część twojej edycji

Wywołanie reset () w wątku IV spowoduje usunięcie poprzedniej instancji klasy A utworzonej w pierwszym wątku i zastąpienie jej nową instancją? Ponadto po wywołaniu reset () w wątku IV inne wątki będą widzieć tylko nowo utworzony obiekt?

jest nieprawidłowe. Tylko dbędzie wskazywać na nowy A(10), i a, bi cnadal będzie punkt do oryginału A(1). Widać to wyraźnie na poniższym krótkim przykładzie.

#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;                                                                                                          
}

(Najwyraźniej nie zawracałem sobie głowy wątkami: to nie ma wpływu na shared_ptr::reset()zachowanie.)

Dane wyjściowe tego kodu to

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

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

Nicu Stiurca
źródło
35
  1. Prawidłowo, shared_ptrużywaj atomowych przyrostów / ubytków wartości zliczania odniesienia.

  2. Standard gwarantuje, że tylko jeden wątek wywoła operator usuwania na udostępnionym obiekcie. Nie jestem pewien, czy konkretnie określa, że ​​ostatnim wątkiem, który usuwa swoją kopię współdzielonego wskaźnika, będzie ten, który wywołuje usuwanie (prawdopodobnie w praktyce tak będzie).

  3. Nie, przechowywany w nim obiekt może być jednocześnie edytowany przez wiele wątków.

EDYCJA: Drobne uzupełnienie, jeśli chcesz dowiedzieć się, jak ogólnie działają wskaźniki współdzielone, możesz zajrzeć do boost::shared_ptrźródła: http://www.boost.org/doc/libs/1_37_0/boost/shared_ptr.hpp .

Nic więcej
źródło
3
1. Kiedy mówisz „Shared_ptrs”, użyj niepodzielnych przyrostów / ubytków wartości zliczania odniesienia. ” Czy masz na myśli, że nie używają żadnej wewnętrznej blokady do atomowego zwiększania / zmniejszania, co powoduje przełączanie kontekstu? Czy w prostym języku wiele wątków może zwiększać / zmniejszać liczbę odwołań bez użycia blokady? Atomowy przyrost jest wykonywany przez specjalne instrukcje atomic_test_and_swap / atomic_test_and_increment?
rahul.deshmukhpatil
@rahul kompilator może używać mutex / lock, ale większość dobrych kompilatorów nie będzie używać mutex / lock na platformach, na których można to zrobić bez blokady.
Bernard
@Bernard: czy masz na myśli to, że zależy to od implementacji "compilers std lib shared_ptr" dla platformy?
rahul.deshmukhpatil
2
Tak. Z mojego rozumienia standard nie mówi, że musi być wolny od zamków. Ale w najnowszych GCC i MSVC jest on wolny od blokad na sprzęcie Intel x86 i myślę, że inne dobre kompilatory prawdopodobnie zrobią to samo, jeśli sprzęt go obsługuje.
Bernard
18

std::shared_ptr nie jest bezpieczny dla wątków.

Współdzielony wskaźnik to para dwóch wskaźników, jeden do obiektu, a drugi do bloku kontrolnego (trzymanie licznika ref, linki do słabych wskaźników ...).

Może istnieć wiele std :: shared_ptr i za każdym razem, gdy uzyskują dostęp do bloku kontrolnego w celu zmiany licznika odniesienia, jest to bezpieczne wątkowo, ale std::shared_ptrsamo w sobie NIE jest bezpieczne dla wątków ani niepodzielne.

Jeśli przypiszesz nowy obiekt do std::shared_ptrchwili, gdy inny wątek go używa, może skończyć się z nowym wskaźnikiem obiektu, ale nadal będzie używać wskaźnika do bloku kontrolnego starego obiektu => CRASH.

Lothar
źródło
4
Można powiedzieć, że pojedyncza std::shared_ptrinstancja nie jest bezpieczna dla wątków. Z referencji std :: shared_ptr: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,
Można to lepiej sformułować. Wystąpienie std::shared_ptr<T>jest zawsze używane jako bezpieczne wątkowo przez wartość (kopiowaną / przenoszoną) poza granice wątku. Wszystkie inne zastosowania std::shared_ptr<T>&są niebezpieczne poza granicami wątków
WhiZTiM