std :: shared_ptr безопасность потока объяснил

106

Я читаю http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html, и мне все еще не понятны некоторые проблемы с безопасностью потоков:

  1. Стандарт гарантирует, что подсчет ссылок является потокобезопасным и независимым от платформы, верно?
  2. Аналогичная проблема - стандартные гарантии, что только один поток (содержащий последнюю ссылку) вызовет удаление для общего объекта, верно?
  3. shared_ptr не гарантирует безопасность потоков для объекта, хранящегося в нем?

РЕДАКТИРОВАТЬ:

Псевдокод:

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

Вызов reset () в потоке IV удалит предыдущий экземпляр класса A, созданный в первом потоке, и заменит его новым экземпляром? Более того, после вызова reset () в потоке IV другие потоки будут видеть только вновь созданный объект?

Тупой
источник
24
Верно, правильно и правильно.
spraff
16
вы должны использовать make_sharedвместоnew
qdii

Ответы:

87

Как отмечали другие, вы правильно поняли свои исходные 3 вопроса.

Но заключительная часть вашего редактирования

Вызов reset () в потоке IV удалит предыдущий экземпляр класса A, созданный в первом потоке, и заменит его новым экземпляром? Более того, после вызова reset () в потоке IV другие потоки будут видеть только вновь созданный объект?

это неверно. Только dбудет указывать на новый A(10), и a, bи cбудет по- прежнему точки к оригиналу A(1). Это хорошо видно на следующем коротком примере.

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

(Ясно, что я не стал беспокоиться о потоковой передаче: это не влияет на shared_ptr::reset() поведение.)

Результатом этого кода является

а: 1 б: 1 в: 1 г: 1

а: 1 б: 1 в: 1 г: 10

Нику Стюрка
источник
35
  1. Правильно, shared_ptrиспользуйте атомарные приращения / уменьшения значения счетчика ссылок.

  2. Стандарт гарантирует, что только один поток вызовет оператор удаления для общего объекта. Я не уверен, что он специально указывает, что последний поток, который удаляет свою копию общего указателя, будет тем, который вызывает удаление (вероятно, на практике так и будет).

  3. Нет, хранящийся в нем объект может одновременно редактироваться несколькими потоками.

РЕДАКТИРОВАТЬ: небольшое продолжение, если вы хотите понять, как общие указатели работают в целом, вы можете посмотреть boost::shared_ptrисточник: http://www.boost.org/doc/libs/1_37_0/boost/shared_ptr.hpp .

Ничего более
источник
3
1. Когда вы говорите «shared_ptrs» используйте атомарные приращения / уменьшения значения счетчика ссылок ». Вы имеете в виду, что они не используют никакой внутренней блокировки для атомарного увеличения / уменьшения, которая переключает контекст? На простом языке могут ли несколько потоков увеличивать / уменьшать счетчик ссылок без использования блокировки? Атомарное приращение выполняется специальными инструкциями atomic_test_and_swap / atomic_test_and_increment?
rahul.deshmukhpatil
@rahul компилятор может использовать мьютекс / блокировку, но большинство хороших компиляторов не будут использовать мьютекс / блокировку на платформах, где это можно сделать без блокировки.
Бернард,
@Bernard: вы имеете в виду, что это зависит от реализации "compilers std lib shared_ptr" для платформы?
rahul.deshmukhpatil
2
Да. Насколько я понимаю, в стандарте не сказано, что он должен быть без блокировки. Но в последних версиях GCC и MSVC он не блокируется на оборудовании Intel x86, и я думаю, что другие хорошие компиляторы, вероятно, будут делать то же самое, если оборудование его поддерживает.
Бернард
18

std::shared_ptr не является потокобезопасным.

Общий указатель - это пара из двух указателей, один на объект, а другой на блок управления (содержащий счетчик ссылок, ссылки на слабые указатели ...).

Может быть несколько std :: shared_ptr, и всякий раз, когда они обращаются к блоку управления для изменения счетчика ссылок, он является потокобезопасным, но std::shared_ptrсам НЕ является потокобезопасным или атомарным.

Если вы назначаете новый объект, std::shared_ptrпока другой поток использует его, он может получить указатель на новый объект, но все еще будет использовать указатель на блок управления старого объекта => CRASH.

Лотар
источник
4
Можно сказать, что отдельный std::shared_ptrэкземпляр не является потокобезопасным. Из ссылки 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 05
Это можно было бы сформулировать лучше. std::shared_ptr<T>Экземпляр гарантируется поточно-когда всегда по значению (копируется / перемещается) через границу резьбы. Любое другое использование std::shared_ptr<T>&небезопасно
вне