Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.17.150.163] |
|
Страницы: (16) [1] 2 3 ... 15 16 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Неожиданно для себя выяснил, что в gcc реализация классов std::shared_ptr и std::weak_ptr не является потоко-безопасной. В частности конструктор копирования для shared_ptr и функция weak_ptr::lock(). Пришлось попытаться
#include <atomic> #include <type_traits> template <typename T> class CSharedPtr; template <typename T> class CWeakPtr; class CRefCounter { public: bool AddRef() { for(;;) { auto cnt = m_cnt.load(); if (!cnt) return false; if (m_cnt.compare_exchange_weak(cnt, cnt + 1)) break; } return true; } bool Release() noexcept { return --m_cnt == 0; } explicit operator bool() const noexcept { return m_cnt != 0; } protected: volatile std::atomic<uint32_t> m_cnt{1}; }; struct CSharedCounter { CRefCounter m_refs; CRefCounter m_weaks; }; struct CSharedRef { CSharedCounter *m_cnt; void *m_p; }; class CSharedPtrLock { public: void lock() noexcept { ++m_cnt; } void unlock() noexcept { --m_cnt; } void Wait() const noexcept { while (m_cnt != 0) std::this_thread::yield(); } protected: volatile std::atomic<uintmax_t> m_cnt{0}; }; template <typename T> CSharedPtrLock &GetSharedPtrLock() { static CSharedPtrLock _lock; return _lock; } class CSharedPtrBase { public: CSharedPtrBase() noexcept { } protected: CSharedPtrBase(CSharedRef ref) noexcept : m_ref(ref) { } CSharedPtrBase(CSharedPtrBase &&sp) noexcept : m_ref(sp.Detach()) { } void _swap(CSharedPtrBase &sp) noexcept { m_ref = sp.m_ref.exchange(m_ref); } CSharedRef Detach() noexcept { return m_ref.exchange({nullptr, nullptr}); } template <typename T> CSharedRef GetSharedRef() const noexcept { std::unique_lock<CSharedPtrLock> lock(GetSharedPtrLock<T>()); auto ref = m_ref.load(); return ref.m_cnt && ref.m_cnt->m_weaks.AddRef()? ref: CSharedRef{nullptr, nullptr}; } volatile std::atomic<CSharedRef> m_ref{{nullptr, nullptr}}; }; template <typename T> class CSharedPtr : public CSharedPtrBase { template <typename T_> friend class CWeakPtr; public: template <typename... TT> static CSharedPtr Make(TT&&... args) { return CSharedPtr(std::make_unique<T>(std::forward<TT>(args)...)); }; CSharedPtr() noexcept { } explicit CSharedPtr(std::unique_ptr<T> &&sp) : CSharedPtrBase({sp? new CSharedCounter(): nullptr, sp.get()}) { sp.release(); } explicit CSharedPtr(T *p) : CSharedPtr(std::unique_ptr<T>(p)) { } CSharedPtr(const CSharedPtr &sp) noexcept : CSharedPtr(sp.GetSharedRef<T>()) { } CSharedPtr(CSharedPtr &&sp) : CSharedPtrBase(sp.Detach()) { } ~CSharedPtr() { auto ref = Detach(); if (!ref.m_cnt) return; if (ref.m_p && ref.m_cnt->m_refs.Release()) delete reinterpret_cast<T *>(ref.m_p); if (ref.m_cnt->m_weaks.Release()) { GetSharedPtrLock<T>().Wait(); delete ref.m_cnt; } } T *get() noexcept { return reinterpret_cast<T *>(m_ref.load().m_p); } void reset() noexcept { CSharedPtr()._swap(*this); } template <typename T_> void reset(T_&& p) { CSharedPtr(std::forward<T_>(p))._swap(*this); } explicit operator bool() const noexcept { return this->m_ref.load().m_p != nullptr; } T *operator->() noexcept { return get(); } T &operator *() noexcept { return *get(); } CSharedPtr &operator =(const CSharedPtr &sp) { if (&sp != this) CSharedPtr(sp)._swap(*this); return *this; } CSharedPtr<T> &operator =(CSharedPtr &&sp) { _swap(sp); return *this; } protected: CSharedPtr(CSharedRef ref) noexcept : CSharedPtrBase({ref.m_cnt, ref.m_cnt && ref.m_p && ref.m_cnt->m_refs.AddRef()? ref.m_p: nullptr}) { } }; template <typename T> class CWeakPtr : public CSharedPtrBase { public: CWeakPtr() noexcept { } CWeakPtr(CSharedPtr<T> sp) noexcept : CSharedPtrBase(sp.GetSharedRef<T>()) { } CWeakPtr(const CWeakPtr &sp) noexcept : CSharedPtrBase(sp.GetSharedRef<T>()) { } CWeakPtr(CWeakPtr &&sp) noexcept : CSharedPtrBase(sp.Detach()) { } ~CWeakPtr() { auto ref = Detach(); if (ref.m_cnt && ref.m_cnt->m_weaks.Release()) { GetSharedPtrLock<T>().Wait(); delete ref.m_cnt; } } CSharedPtr<T> lock() const noexcept { return CSharedPtr<T>(GetSharedRef<T>()); } void reset() noexcept { CWeakPtr()._swap(*this); } template <typename T_> CWeakPtr &operator =(CSharedPtr<T_> sp) { CWeakPtr(std::move(sp))._swap(*this); return *this; } CWeakPtr &operator =(const CWeakPtr &sp) { if (&sp != this) CWeakPtr(sp)._swap(*this); return *this; } }; Прикреплённый файлSharedPtr.h (4,43 Кбайт, скачиваний: 145) |
Сообщ.
#2
,
|
|
|
синхронизация на что дана?
|
Сообщ.
#3
,
|
|
|
В смысле? Синхронизацию чего с чем ты имеешь ввиду?
|
Сообщ.
#4
,
|
|
|
Цитата Олег М @ Пришлось попытаться Сразу и не разберёшься - много текста. На словах скажи, как синхронизируется инкремент/декремент ссылок на обьект при работе SharedPtr-ми из разных потоков. |
Сообщ.
#5
,
|
|
|
Цитата Олег М @ Ну, на словах - увеличиваешь счётчик, если он не равен нулю, CRefCouner::AddRef(). Этот ответ требовался? Нет. Как синхронизируется... Из одного потока производится инкремент и вдруг.. из другого потока декремент. |
Сообщ.
#6
,
|
|
|
На словах я это и так знаю. Мне хочется понять, почему мой код не работает. Если не работает.
Добавлено Цитата ЫукпШ @ Цитата Олег М @ Ну, на словах - увеличиваешь счётчик, если он не равен нулю, CRefCouner::AddRef(). Этот ответ требовался? Нет. Как синхронизируется... Из одного потока производится инкремент и вдруг.. из другого потока декремент. Насчет "вдруг" можно поподробнее, в каком случае это может произойти, и к чему приведет? |
Сообщ.
#7
,
|
|
|
Цитата Олег М @ Насчет "вдруг" можно поподробнее, в каком случае это может произойти, и к чему приведет? "Вдруг" - это реальная работа из разных потоков. Все действия алгоритма по инкременту/декременту и принятия решения об уничтожении объекта не являются атомарными. А значит требуют синхронизации при работе из разных потоков. 1. Возможна такая коллизия - SP_n1 сделал декремент, обнаружил, что счётчик стал равен 0 и решил уничтожить объект. Как никому уже не нужный, но не успел это сделать. В этот момент его грубо прервали. Из другого потока наоборот - SP_n2 счётчик увеличил, оставаясь в полной уверенности, что объект существует. Далее продолжил работу SP_n1 и уничтожил объект. 2. Коллизия по-проще - допустим, сама операция инкремента/декремента не атомарна. Значит, любая из них может быть прервана не полностю завершённой, другой такой-же операцией из другого потока. С непредсказуемыми последствиями. 3. я не разбирался точно, как там у тебя всё устроено. Думаю, что вместе с получением самого первого адреса на объект, в памяти создаётся структура, содержащая счётчик ссылок и указатель на объект. Адрес на эту структуру получают все SP, работающие с одним и тем же объектом. И эта структура в памяти должна уничтожаться вместе с объектом. И все эти действия тоже должны быть сделаны "потокобезопасными". |
Сообщ.
#8
,
|
|
|
Да, есть такие проблемы. Есть и другие. В моем коде они решены. Я просил оценить свежим взглядом – так ли это, или я чего–то упустил?
|
Сообщ.
#9
,
|
|
|
Читаем документацию на shared_ptr:
Цитата All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. 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; the shared_ptr overloads of atomic functions can be used to prevent the data race. http://en.cppreference.com/w/cpp/memory/shared_ptr Из неё следует, что при работе с std::shared_ptr защищать необходимо лишь многопоточные операции с одной копией shared_ptr. Если два потока работают с разными копиями указателя на один объект - всё будет корректно. Следовательно, городить огород со своими велосипедами особого смысла нет. Копирование/присваивание/перенос shared_ptr можно делать под внешней лочкой (если она вообще необходима). Но в качестве практики свой "умный указатель", конечно, написать полезно. |
Сообщ.
#10
,
|
|
|
Проблема в одновременном вызове конструктора копирования и функции release. Это не работает, в реализации gcc
|
Сообщ.
#11
,
|
|
|
Цитата Олег М @ Проблема в одновременном вызове конструктора копирования и функции release. Это не работает, в реализации gcc Если у одной и той же копии shared_ptr - то и не должно работать. |
Сообщ.
#12
,
|
|
|
Очевидно, что у разных. Надо постараться, чтоб у одного объекта вызвать одновременно конструктор и метод. Вообще - в одном потоке вызываешь weak_ptr::lock, в другом - shared_ptr::release, и всё, получаешь segmentation fault
|
Сообщ.
#13
,
|
|
|
Цитата Олег М @ shared_ptr::release Эм. У shared_ptr есть release? Он вроде только у unique_ptr. Цитата и всё, получаешь segmentation fault Приведи код. |
Сообщ.
#14
,
|
|
|
Цитата D_KEY @ Цитата Олег М @ shared_ptr::release Эм. У shared_ptr есть release? Он вроде только у unique_ptr. Цитата и всё, получаешь segmentation fault Приведи код. Да, ошибся - shared_ptr::reset Код сейчас сделаю Добавлено Это код который не работает на std::shared_ptr и работает на моих классах std::shared_ptr<std::string> sp; std::weak_ptr<std::string> sp2; //CSharedPtr<std::string> sp; //CWeakPtr<std::string> sp2; volatile bool stop = false; std::list<std::unique_ptr<std::thread>> threads; threads.emplace_back(std::make_unique<std::thread>([&sp, &sp2, &stop]() { while(!stop) { sp.reset(new std::string("!!!!!!!!!!!!!!!!!!!!!!!!!!!!11111111111111111111111111111111111111")); sp2 = sp; } })); threads.emplace_back(std::make_unique<std::thread>([&sp2, &stop]() { int i = 0; while(!stop) { auto sp = sp2.lock(); if (sp) std::cout << (++i) << ", " << *sp << std::endl; std::this_thread::sleep_for(10ms); } })); WaitStop(); stop = true; for (auto &item: threads) item->join(); |
Сообщ.
#15
,
|
|
|
Но так и не должен этот код работать - все твои потоки конкурентно долбятся к двум умным указателям, а не со своими собственными копиями работают. Как раз про такой кейс в доке и сказано, что data race будет.
|