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
Правильно, правильно і правильно.
шприц

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()поведінку.)

Вихід цього коду є

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

a: 1 b: 1 c: 1 d: 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_inmentment?
rahul.deshmukhpatil

@rahul компілятор вільний використовувати mutex / lock, але більшість хороших компіляторів не використовуватимуть mutex / lock на платформах, де це можна зробити без замка.
Бернард

@Bernard: ти маєш на увазі, що це залежить від реалізації "компіляторів std lib shared_ptr" для платформи?
rahul.deshmukhpatil

2
Так. З мого розуміння, стандарт не говорить про те, що він повинен бути без замка. Але в останніх GCC та MSVC він заблокований на апаратному забезпеченні Intel x86, і я думаю, що інші хороші компілятори, ймовірно, зроблять те саме, коли апаратне забезпечення підтримує його.
Бернар

18

std::shared_ptr не є безпечним для ниток.

Спільний покажчик - це пара з двох покажчиків, один на об'єкт і один на блок управління (тримає лічильник ref, посилається на слабкі вказівники ...).

Тут може бути декілька 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

Це можна сказати краще. std::shared_ptr<T>Примірник гарантується поточно-коли завжди за значенням (копіюється / переміщується) через кордон різьблення. Усі інші std::shared_ptr<T>&
сфери
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.