LINUX.ORG.RU

Удаление shared_ptr, а нужна ли дополнительная синхронизация?

 ,


0

2

Привет. Вопрос из прошлого, тогда я пришел к выводу, что дополнительную синхронизацию лучше делать, но хз, сейчас подошёл к подобному вопросу вновь. Ленюсь копать в сети, в прошлый раз однозначного ответа не нашел, вроде. Пример:

#include <thread>
#include <vector>
#include <memory>
#include <chrono>
#include <iostream>
#include <mutex>
using namespace std;


class Q {
    mutex m;
    int data {0};
public:
    void incr() {
        lock_guard lck(m);
        ++ data;
    }
    ~Q() {
        //lock_guard lck(m);
        cerr << data << endl;
    }
};

void th_fn(shared_ptr<Q> q) {
    q->incr();
    q.reset();
    while (true);
}

int main() {
    auto ob = make_shared<Q>();
    vector<thread> v;
    for (int i = 0;  i < 5;  ++ i)
        v.emplace_back(th_fn, ob);
    this_thread::sleep_for(3s);
    ob.reset();
    for (auto &t : v)
        t.join();
}

Нужно ли брать мьютекс в деструкторе? TSAN не выдает ошибок при отсутсвующем взятие, но вроде как декремент счетчика в shared_ptr - relaxed операция (или нет?). Какие мысли?



Последнее исправление: kvpfs_2 (всего исправлений: 2)
Ответ на: комментарий от PRN

Ну это понятно. Вопрос то в том а является ли декремент счетчика shared_ptr acquire/release операцией, если так, тот поток, который увидел 0, вызывает деструктор и является синхронизированным со всеми потоками, которые удалили shared_ptr ранее

kvpfs_2
() автор топика
Ответ на: комментарий от kvpfs_2

Плохо смотрел)) https://en.cppreference.com/w/cpp/memory/shared_ptr

To satisfy thread safety requirements, the reference counters are typically incremented using an equivalent of std::atomic::fetch_add with std::memory_order_relaxed (decrementing requires stronger ordering to safely destroy the control block). 
PRN
()
Ответ на: комментарий от PRN

Ну а какая тут конкретика? Что в typical implementation «decrementing requires stronger ordering to safely destroy the control block» ну как бы можно было бы и побольше написать. И если имплементейшен не typical?

Мне, конечно, хочется верить, что синхронизация имеется и никому в голову не придет делать relaxed декремент, но каких-то железобетонных гарантий я не вижу.

kvpfs_2
() автор топика

У каждого потока копия shared_ptr на экземпляр Q (в функцию th_fn указатель передается по значению), пока жива хотя бы одна копия объект будет жив. Декремент счетчика в shared_ptr , выполняется с семантикой release, соответственно вызов q->incr() не может быть перепланирован позже атомарного декремента в shared_ptr, если точнее соседние потоки никогда не увидят инкремент в Q позже декремента счетчика в shared_ptr. Хотя для этого сферического в вакууме примера это значения не имеет, все равно никто не использует экземпляр Q после разрушения локальной копии shared_ptr в каждом треде.

Прикрывать мьютексом обращения к полям (в данном случаи только инкремент) в Q - да, прикрывать мютексом (что?) в деструкторе - нет.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

выполняется с семантикой release

Я вообще хз откуда эта инфа, что что-то там выполняется с какой-то семантикой, я ничего не нашёл. Т.е. relaxed декремент - это вполне норм и стандарт не нарушает.

Ладно, в общем я всё же верю в адекватность реализаторов, и в то что декремент/чтение счетчика это release/acquire операция, и никому разумному что-то здесь «заоптимизировать» не захочется. Если только ребятам из шланга с их говнооптимизациями ради говнооптимизаций (вроде завершения вечных циклов или выкинуть обращение в NULL).

kvpfs_2
() автор топика