std :: shared_ptr thread safety explicada

106

Estou lendo http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html e alguns problemas de segurança de thread ainda não estão claros para mim:

  1. Padrão garante que a contagem de referência seja tratada com segurança de thread e seja independente de plataforma, certo?
  2. Problema semelhante - o padrão garante que apenas um encadeamento (segurando a última referência) irá chamar delete no objeto compartilhado, certo?
  3. shared_ptr não garante qualquer segurança de thread para objeto armazenado nele?

EDITAR:

Pseudo-código:

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

Chamar reset () no encadeamento IV excluirá a instância anterior da classe A criada no primeiro encadeamento e a substituirá por uma nova instância? Além disso, após chamar reset () no thread IV, os outros threads verão apenas o objeto recém-criado?

Pateta
fonte
24
Certo, certo e certo.
Spraff
16
você deve usar em make_sharedvez denew
qdii

Respostas:

87

Como outros indicaram, você entendeu corretamente em relação às suas 3 perguntas originais.

Mas a parte final da sua edição

Chamar reset () no encadeamento IV excluirá a instância anterior da classe A criada no primeiro encadeamento e a substituirá por uma nova instância? Além disso, após chamar reset () no thread IV, os outros threads verão apenas o objeto recém-criado?

está incorreto. Só dvai apontar para o novo A(10), e a, be ccontinuará a ponto com o original A(1). Isso pode ser visto claramente no exemplo a seguir.

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

(Claramente, eu não me incomodei com nenhum threading: isso não influencia o shared_ptr::reset()comportamento.)

A saída deste código é

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

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

Nicu Stiurca
fonte
35
  1. Correto, shared_ptruse incrementos / decrementos atômicos de um valor de contagem de referência.

  2. O padrão garante que apenas um thread chamará o operador delete em um objeto compartilhado. Não tenho certeza se ele especifica especificamente que o último thread que exclui sua cópia do ponteiro compartilhado será aquele que chama delete (provavelmente, na prática, esse seria o caso).

  3. Não, eles não fazem, o objeto armazenado nele pode ser editado simultaneamente por vários threads.

EDIT: Acompanhamento ligeiro, se você deseja ter uma idéia de como os ponteiros compartilhados funcionam em geral, você pode querer olhar a boost::shared_ptrfonte: http://www.boost.org/doc/libs/1_37_0/boost/shared_ptr.hpp .

Nada mais
fonte
3
1. Quando você diz "'shared_ptrs', use incrementos / decrementos atômicos de um valor de contagem de referência." Você quer dizer que eles não usam nenhum bloqueio interno para incremento / decremento atômico, o que muda o contexto? Em uma linguagem simples, vários threads podem aumentar / diminuir a contagem de referência sem usar o bloqueio? O incremento atômico é feito por instruções especiais atomic_test_and_swap / atomic_test_and_increment?
rahul.deshmukhpatil de
@rahul o compilador é livre para usar mutex / lock, mas a maioria dos bons compiladores não usará mutex / lock em plataformas onde isso pode ser feito sem lock.
Bernard
@Bernard: você quer dizer que depende da implementação de "compiladores std lib shared_ptr" para a plataforma?
rahul.deshmukhpatil
2
Sim. Do meu entendimento, o padrão não diz que deve ser bloqueado. Mas no GCC e MSVC mais recente, ele não tem bloqueio no hardware Intel x86, e acho que outros bons compiladores provavelmente farão o mesmo quando o hardware o suportar.
Bernard
18

std::shared_ptr não é thread-safe.

Um ponteiro compartilhado é um par de dois ponteiros, um para o objeto e outro para um bloco de controle (segurando o contador ref, links para ponteiros fracos ...).

Pode haver vários std :: shared_ptr e sempre que eles acessam o bloco de controle para alterar o contador de referência é thread-safe, mas o std::shared_ptrpróprio NÃO é thread-safe ou atômico.

Se você atribuir um novo objeto a std::shared_ptrenquanto outro thread o usa, ele pode acabar com o novo ponteiro do objeto, mas ainda usando um ponteiro para o bloco de controle do objeto antigo => CRASH.

Lothar
fonte
4
Poderíamos dizer que a std::shared_ptrinstância única não é thread-safe. Da referência 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
Isso poderia ser melhor formulado. Uma std::shared_ptr<T>instância é garantida para thread-safe quando sempre usada por valor (copiado / movido) entre os limites do thread. Todos os outros usos std::shared_ptr<T>&não são seguros entre os limites do thread
WhiZTiM