О потоковой безопасности weak_ptr

std::shared_ptr<int> g_s = std::make_shared<int>(1);
void f1()
{
    std::shared_ptr<int>l_s1 = g_s; // read g_s
}

void f2()
{
    std::shared_ptr<int> l_s2 = std::make_shared<int>(3);
    std::thread th(f1);
    th.detach();
    g_s = l_s2; // write g_s
}

Что касается вышеприведенного кода, я знаю, что разные потоки, читающие и записывающие один и тот же shared_ptr, приводят к условиям гонки. Но как насчет weak_ptr? Есть ли какое-либо условие гонки в коде ниже? (Моя платформа - Microsoft VS2013.)

std::weak_ptr<int> g_w;

void f3()
{
    std::shared_ptr<int>l_s3 = g_w.lock(); //2. here will read g_w
    if (l_s3)
    {
        ;/.....
    }
}

void f4()
{
    std::shared_ptr<int> p_s = std::make_shared<int>(1);
    g_w = p_s;

    std::thread th(f3);
    th.detach();
    // 1. p_s destory will motify g_w (write g_w)
}

Ответ 1

Я знаю, что опаздываю, но это происходит при поиске "потока слабой_программы", и ответ Кейси - это не вся правда. Оба shared_ptr и weak_ptr могут использоваться из потоков без дальнейшей синхронизации.

Для shared_ptr существует много документации (например, на cppreference.com или на fooobar.com/questions/71510/...). Вы можете безопасно получить доступ к shared_ptr, которые указывают на один и тот же объект из разных потоков. Вы просто не можете ударить по одному указателю из двух потоков. Другими словами:

// Using p and p_copy from two threads is fine.
// Using p from two threads or p and p_ref from two threads is illegal.
std::shared_ptr<A> p = std::make_shared<A>();
std::shared_ptr<A> &p_ref = p;
std::shared_ptr<A> p_copy = p;

Чтобы решить эту проблему в вашем коде, перейдите g_s в качестве параметра (по значению) * в f1().

Для слабых указателей гарантия безопасности скрыта в документации для weak_ptr:: lock:

Эффективно возвращает expired() ? shared_ptr<T>() : shared_ptr<T>(*this), выполняемый атомарно.

Вы можете использовать weak_ptr::lock(), чтобы получить shared_ptr из других потоков без дальнейшей синхронизации. Это также подтверждено здесь для Boost и этот ответ SO от Chris Jester-Young.

Опять же, вы должны не изменять один и тот же weak_ptr из одного потока при обращении к нему из другого, поэтому передайте g_w в f3() по значению.

Ответ 2

shared_ptr и weak_ptr подпадают под одни и те же требования к требованиям к одежде, как и все другие стандартные типы библиотек: одновременные вызовы функций-членов должны быть потокобезопасными, если эти функции-члены не изменяются (const) (Подробно в C + +11 §17.6.5.9 Избегание гонки данных [res.data.races]). Операторы присваивания, в частности, не const.

Ответ 3

Для краткости в последующем обсуждении, различные weak_ptr и shared_ptr, которые сгенерированы из одного и того же исходного shared_ptr или unique_ptr будут называться "экземплярами". weak_ptr и shared_ptr, которые не разделяют один и тот же объект, не должны рассматриваться в данном анализе. Общие правила оценки безопасности потока:

  1. Одновременные вызовы функций-членов const в одном и том же экземпляре поточно-ориентированы. Все функции наблюдателя являются const.
  2. Одновременные вызовы в разных экземплярах являются поточно-ориентированными, даже если один из вызовов является модификатором.
  3. Одновременные вызовы в одном и том же экземпляре, когда хотя бы один из вызовов является модификатором, не являются потокобезопасными.

В следующей таблице показана безопасность потоков, когда два потока работают в одном и том же экземпляре одновременно.

+---------------+----------+-------------------------+------------------------+
|   operation   |   type   | other thread modifying  | other thread observing |
+---------------+----------+-------------------------+------------------------+
| (constructor) |          | not applicable          | not applicable         |
| (destructor)  |          | unsafe                  | unsafe                 |
| operator=     | modifier | unsafe                  | unsafe                 |
| reset         | modifier | unsafe                  | unsafe                 |
| swap          | modifier | unsafe                  | unsafe                 |
| use_count     | observer | unsafe                  | safe                   |
| expired       | observer | unsafe                  | safe                   |
| lock          | observer | unsafe                  | safe                   |
| owner_before  | observer | unsafe                  | safe                   |
+---------------+----------+-------------------------+------------------------+

Обсуждение cppreference в std :: atomic (std :: weak_ptr) является наглядным в отношении безопасности одновременного доступа к различным экземплярам:

Обратите внимание, что управляющий блок, используемый std :: weak_ptr и std :: shared_ptr, является потокобезопасным: к различным неатомарным объектам std :: weak_ptr можно получить доступ с помощью изменяемых операций, таких как operator = или reset, одновременно несколькими потоками, даже когда эти экземпляры являются копиями или иным образом совместно используют один и тот же блок управления внутри.

С++ 20 вводит специализацию слабого указателя std::atomic которая обеспечивает поточно-ориентированное изменение одного и того же экземпляра посредством соответствующей синхронизации. Обратите внимание, что когда дело доходит до конструкторов, инициализация из другого экземпляра не является атомарной. Например, atomic<weak_ptr<T>> myptr(anotherWeakPtr); это не атомарная операция.