На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
[!] Как относитесь к модерированию на этом форуме? Выскажите свое мнение здесь
Модераторы: Qraizer
Страницы: (16) [1] 2 3 ...  15 16 все  ( Перейти к последнему сообщению )  
> Thread-safe shared_ptr , Реализация потоко-безопасного SharedPtr
    Неожиданно для себя выяснил, что в gcc реализация классов std::shared_ptr и std::weak_ptr не является потоко-безопасной. В частности конструктор копирования для shared_ptr и функция weak_ptr::lock(). Пришлось попытаться изобрести велосипед написать собственную версию. Прошу критики

    ExpandedWrap disabled
      #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)
    Сообщение отредактировано: Олег М -
      синхронизация на что дана? :D
        В смысле? Синхронизацию чего с чем ты имеешь ввиду?
          Цитата Олег М @
          Пришлось попытаться изобрести велосипед написать собственную версию.

          Сразу и не разберёшься - много текста.
          На словах скажи, как синхронизируется инкремент/декремент ссылок
          на обьект при работе SharedPtr-ми из разных потоков.
            Цитата Олег М @
            Ну, на словах - увеличиваешь счётчик, если он не равен нулю, CRefCouner::AddRef(). Этот ответ требовался?

            Нет. Как синхронизируется...
            Из одного потока производится инкремент и вдруг.. из другого
            потока декремент.
              На словах я это и так знаю. Мне хочется понять, почему мой код не работает. Если не работает.

              Добавлено
              Цитата ЫукпШ @
              Цитата Олег М @
              Ну, на словах - увеличиваешь счётчик, если он не равен нулю, CRefCouner::AddRef(). Этот ответ требовался?

              Нет. Как синхронизируется...
              Из одного потока производится инкремент и вдруг.. из другого
              потока декремент.

              Насчет "вдруг" можно поподробнее, в каком случае это может произойти, и к чему приведет?
                Цитата Олег М @
                Насчет "вдруг" можно поподробнее, в каком случае это может произойти, и к чему приведет?

                "Вдруг" - это реальная работа из разных потоков.

                Все действия алгоритма по инкременту/декременту и принятия решения об уничтожении
                объекта не являются атомарными. А значит требуют синхронизации при работе из разных
                потоков.

                1. Возможна такая коллизия - SP_n1 сделал декремент,
                обнаружил, что счётчик стал равен 0 и решил уничтожить объект.
                Как никому уже не нужный, но не успел это сделать.
                В этот момент его грубо прервали.
                Из другого потока наоборот - SP_n2 счётчик увеличил, оставаясь
                в полной уверенности, что объект существует.
                Далее продолжил работу SP_n1 и уничтожил объект.

                2. Коллизия по-проще - допустим, сама операция инкремента/декремента
                не атомарна. Значит, любая из них может быть прервана не полностю завершённой,
                другой такой-же операцией из другого потока. С непредсказуемыми последствиями.

                3. я не разбирался точно, как там у тебя всё устроено.
                Думаю, что вместе с получением самого первого адреса на объект, в памяти создаётся структура,
                содержащая счётчик ссылок и указатель на объект. Адрес на эту структуру получают все SP, работающие
                с одним и тем же объектом. И эта структура в памяти должна уничтожаться
                вместе с объектом. И все эти действия тоже должны быть сделаны "потокобезопасными".
                Сообщение отредактировано: ЫукпШ -
                  Да, есть такие проблемы. Есть и другие. В моем коде они решены. Я просил оценить свежим взглядом – так ли это, или я чего–то упустил?
                    Читаем документацию на 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 можно делать под внешней лочкой (если она вообще необходима). Но в качестве практики свой "умный указатель", конечно, написать полезно. :)
                      Проблема в одновременном вызове конструктора копирования и функции release. Это не работает, в реализации gcc
                      Сообщение отредактировано: Олег М -
                        Цитата Олег М @
                        Проблема в одновременном вызове конструктора копирования и функции release. Это не работает, в реализации gcc

                        Если у одной и той же копии shared_ptr - то и не должно работать.
                          Очевидно, что у разных. Надо постараться, чтоб у одного объекта вызвать одновременно конструктор и метод. Вообще - в одном потоке вызываешь weak_ptr::lock, в другом - shared_ptr::release, и всё, получаешь segmentation fault
                            Цитата Олег М @
                            shared_ptr::release

                            Эм. У shared_ptr есть release? Он вроде только у unique_ptr.

                            Цитата
                            и всё, получаешь segmentation fault

                            Приведи код.
                              Цитата D_KEY @
                              Цитата Олег М @
                              shared_ptr::release

                              Эм. У shared_ptr есть release? Он вроде только у unique_ptr.

                              Цитата
                              и всё, получаешь segmentation fault

                              Приведи код.

                              Да, ошибся - shared_ptr::reset
                              Код сейчас сделаю

                              Добавлено
                              Это код который не работает на std::shared_ptr и работает на моих классах
                              ExpandedWrap disabled
                                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();
                              Сообщение отредактировано: Олег М -
                                Но так и не должен этот код работать - все твои потоки конкурентно долбятся к двум умным указателям, а не со своими собственными копиями работают. Как раз про такой кейс в доке и сказано, что data race будет.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (16) [1] 2 3 ...  15 16 все


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0453 ]   [ 18 queries used ]   [ Generated: 19.04.24, 20:25 GMT ]