std :: shared_ptr thread safety

Eu li isso

“Vários encadeamentos podem ler e gravar simultaneamente diferentes objects shared_ptr, mesmo quando os objects são cópias que compartilham a propriedade.” ( MSDN: Segurança de thread na biblioteca C ++ padrão )

Isso significa que alterar o object shared_ptr é seguro?
Por exemplo, o próximo código é considerado seguro:

shared_ptr global = make_shared(); ... //In thread 1 shared_ptr private = global; ... //In thread 2 global = make_shared(); ... 

Posso ter certeza de que o thread 1 private terá o valor original de global ou o novo valor que o thread 2 atribuiu, mas de qualquer forma ele terá um shared_ptr válido para myClass?

== EDIT ==
Apenas para explicar minha motivação. Eu quero ter um ponteiro compartilhado para manter minha configuração e eu tenho um pool de segmentos para lidar com solicitações.
tão global é a configuração global.
thread 1 está tomando a configuração atual quando começa a manipular uma solicitação.
thread 2 está atualizando a configuração. (aplica-se apenas a pedidos futuros)

Se funcionar, posso atualizar a configuração dessa maneira sem quebrá-la no meio de um tratamento de solicitação.

O que você está lendo não significa o que você acha que significa. Primeiro de tudo, tente a página msdn do próprio shared_ptr .

Role para baixo na seção “Comentários” e você vai chegar à carne da questão. Basicamente, um shared_ptr<> aponta para um “bloco de controle”, que é como ele controla quantos objects shared_ptr<> estão apontando para o object “Real”. Então, quando você faz isso:

 shared_ptr ptr1 = make_shared(); 

Embora haja apenas uma chamada para alocar memory aqui via make_shared , existem dois blocos “lógicos” que você não deve tratar da mesma forma. Um é o int que armazena o valor real, e o outro é o bloco de controle, que armazena todo o shared_ptr<> “magic” que o faz funcionar.

É apenas o bloco de controle em si que é thread-safe.

Eu coloquei isso em sua própria linha para dar ênfase. O conteúdo do shared_ptr não é thread-safe, nem está gravando na mesma instância shared_ptr . Aqui está algo para demonstrar o que quero dizer:

 // In main() shared_ptr global_instance = make_shared(); // (launch all other threads AFTER global_instance is fully constructed) //In thread 1 shared_ptr local_instance = global_instance; 

Isso é bom, na verdade você pode fazer isso em todos os segmentos, tanto quanto você quiser. E quando local_instance é destruído (saindo do escopo), ele também é thread-safe. Alguém pode estar acessando o global_instance e isso não fará diferença. O trecho extraído do msdn basicamente significa que “o access ao bloco de controle é thread-safe”, de modo que outras instâncias shared_ptr<> possam ser criadas e destruídas em diferentes threads, tanto quanto necessário.

 //In thread 1 local_instance = make_shared(); 

Isto é bom. Isso afetará o object global_instance , mas apenas indiretamente. O bloco de controle para o qual ele aponta será decrementado, mas feito de maneira segura. local_instance não apontará mais para o mesmo object (ou bloco de controle) como global_instance .

 //In thread 2 global_instance = make_shared(); 

Isso certamente não é bom se global_instance for acessado de qualquer outro global_instance (o que você diz que está fazendo). Ele precisa de um bloqueio se você estiver fazendo isso porque está escrevendo para onde o global_instance mora, não apenas lendo dele. Portanto, gravar em um object de vários segmentos é ruim, a menos que você o tenha protegido por meio de um bloqueio. Assim, você pode ler a partir de global_instance o object, atribuindo novos objects shared_ptr<> partir dele, mas não pode escrever nele.

 // In thread 3 *global_instance = 3; int a = *global_instance; // In thread 4 *global_instance = 7; 

O valor de a é indefinido. Pode ser 7, ou pode ser 3, ou pode ser qualquer outra coisa também. A segurança de encadeamento das instâncias shared_ptr<> aplica-se apenas ao gerenciamento de instâncias shared_ptr<> que foram inicializadas umas das outras, não para o que estão apontando.

Para enfatizar o que quero dizer, olhe para isto:

 shared_ptr global_instance = make_shared(0); void thread_fcn(); int main(int argc, char** argv) { thread thread1(thread_fcn); thread thread2(thread_fcn); ... thread thread10(thread_fcn); chrono::milliseconds duration(10000); this_thread::sleep_for(duration); return; } void thread_fcn() { // This is thread-safe and will work fine, though it's useless. Many // short-lived pointers will be created and destroyed. for(int i = 0; i < 10000; i++) { shared_ptr temp = global_instance; } // This is not thread-safe. While all the threads are the same, the // "final" value of this is almost certainly NOT going to be // number_of_threads*10000 = 100,000. It'll be something else. for(int i = 0; i < 10000; i++) { *global_instance = *global_instance + 1; } } 

Um shared_ptr<> é um mecanismo para garantir que vários proprietários de objects garantam que um object seja destruído, não um mecanismo para garantir que vários encadeamentos possam acessar um object corretamente. Você ainda precisa de um mecanismo de synchronization separado para usá-lo com segurança em vários segmentos (como std :: mutex ).

A melhor maneira de pensar sobre isso é que o shared_ptr<> garante que várias cópias que apontam para a mesma memory não tenham problemas de synchronization, mas não faz nada pelo object apontado. Trate assim.

Para adicionar ao que Kevin escreveu, a especificação C ++ 14 possui suporte adicional para access atômico a objects shared_ptr:

20.8.2.6 access atômico shared_ptr [util.smartptr.shared.atomic]

O access simultâneo a um object shared_ptr de vários encadeamentos não introduz uma corrida de dados se o access for feito exclusivamente por meio das funções nesta seção e a instância for passada como seu primeiro argumento.

Então, se você fizer:

 //In thread 1 shared_ptr private = atomic_load(&global); ... //In thread 2 atomic_store(&global, make_shared()); ... 

será thread-safe.

Isso significa que você terá um shared_ptr válido e uma contagem de referência válida.

Você está descrevendo uma condição de corrida entre dois segmentos que estão tentando ler / atribuir à mesma variável.

Como esse comportamento é indefinido em geral (só faz sentido no contexto e no tempo do programa individual) shared_ptr não lida com isso.

As operações de leitura não estão sujeitas a disputas de dados entre si, portanto, é seguro compartilhar a mesma instância do shared_ptr entre os encadeamentos, desde que todos os encadeamentos usem apenas methods const (isso inclui criar cópias dele). Assim que um thread usa o método non-const (como em “apontá-lo para outro object”), esse uso não é mais seguro para thread.

O exemplo OP não é thread-safe e exigiria o uso de carga atômica no thread 1. e armazenamento atômico no thread 2 (seção 2.7.2.5 em C ++ 11) para torná-lo thread seguro.

A palavra-chave no texto do MSDN é de fato objects shared_ptr diferentes , como já foi dito nas respostas anteriores.