На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
Дорогие друзья! Поздравляем вас с днём Победы!
msm.ru
[!] Как относитесь к модерированию на этом форуме? Выскажите свое мнение здесь
Модераторы: Qraizer
Страницы: (16) « Первая ... 11 12 [13] 14 15 ... Последняя » все  ( Перейти к последнему сообщению )  
> Thread-safe shared_ptr , Реализация потоко-безопасного SharedPtr
    Ну да. Что в этом плохого? Имея ссылку на CSharedPtr, ты можешь в любом потоке сделать копию, которая будет гарантировать, что объект, на который тот указывает, не удалится, либо уже был удалён.
      Цитата Олег М @
      Ну да. Что в этом плохого?

      Плохого в этом именно вот это и есть:
      Цитата Олег М @
      Имея ссылку на CSharedPtr, ты можешь в любом потоке сделать копию, которая будет гарантировать, что объект, на который тот указывает, не удалится, либо уже был удалён.

      Ты совмещаешь два контракта, скрещиваешь ежа с ужом. С одной стороны, ты хочешь, чтобы время жизни контролировалось экструзивно, с другой - чтобы изменения, сделанные в одном потоке, были доступны в другом. И указатель себя при этом прекрасно чувствовал. И вот мне не совсем ясно - зачем тебе это именно в таком виде требуется? Что мешает честно разделить контракты?
      Сообщение отредактировано: Flex Ferrum -
        Я так и не понял - почему нельзя передавать в другой поток shared_ptr тупо по значению?
          Цитата OpenGL @
          Я так и не понял - почему нельзя передавать в другой поток shared_ptr тупо по значению?

          Потому что тогда изменения этого указателя в одном потоке не зааффектят другой. А именно это требуется.
            Вспомнил ещё одну задачу, которую я решаю при помощи shared_ptr:
            Есть объект, достаточно сложный, который постоянно перезаписывается в одном потоке. И есть несколько потоков, которые читают этот объект, причём операция чтения достаточно долгая - форматирование и всё такое. Чтобы чтение не блокировало запись, я в потоке который пишет создаю новый объект и просто подменяю указатель. Вот как то так:

            ExpandedWrap disabled
              class CWriter
              {
                  void ProcessWrite()
                  {
                      auto sp = std::make_unique<Cobject>();
                      m_sp = std::move(sp);
                  }
                  
                  CSharedPtr<CObject> m_sp;
              };
               
              class CReader
              {
                  void ProcessRead(CWriter &src)
                  {
                      auto sp = src.m_sp;
                      if (sp)
                          sp->Format();
                  }
              };
              В общем, возьмись я делать что-то подобное, я бы наколходил что-то типа этого:
              ExpandedWrap disabled
                #include <iostream>
                #include <atomic>
                #include <memory>
                #include <thread>
                #include <mutex>
                #include <chrono>
                #include <list>
                #include <string>
                 
                using namespace std::chrono_literals;
                 
                namespace test
                {
                // Taken from boost examples
                class SpinLock
                {
                public:
                  SpinLock() : m_state(Unlocked) {}
                 
                  void lock()
                  {
                    while (m_state.exchange(Locked, std::memory_order_acquire) == Locked)
                    {        
                      /* busy-wait and no yield */
                    }
                  }
                  void unlock()
                  {
                    m_state.store(Unlocked, std::memory_order_release);
                  }
                  
                private:
                  enum LockState
                  {
                      Locked,
                      Unlocked
                  };
                  
                  std::atomic<LockState> m_state;
                };
                 
                template<typename T>
                class ObjectHolder
                {
                public:
                    using Holder = std::shared_ptr<T>;
                    ObjectHolder() = default;
                    explicit ObjectHolder(T* ptr)
                        : m_object(ptr)
                    {
                    }
                    ~ObjectHolder() = default;
                 
                    Holder Lock() const
                    {
                        std::lock_guard<SpinLock> lg(m_guard);
                        return m_object;
                    }
                    
                    void Reset(T* obj = nullptr)
                    {
                        Holder newHolder(obj);
                        {
                            std::lock_guard<SpinLock> lg(m_guard);
                            m_object.swap(newHolder);
                        }
                    }
                    
                    void Reset(Holder newHolder)
                    {
                        std::lock_guard<SpinLock> lg(m_guard);
                        m_object.swap(newHolder);
                    }
                    
                private:
                    mutable SpinLock m_guard;
                    Holder m_object;
                };
                } // test_with_mutex
                 
                int main()
                {
                    std::cout << "Hello from TestSharedPointer. sizeof(m) = " << std::endl;
                    
                    volatile bool stop = false;
                    std::list<std::unique_ptr<std::thread>> threads;
                    test::ObjectHolder<std::string> obj;
                    
                    
                    for (int n = 0; n < 10; ++ n)
                    {
                        threads.push_back(std::make_unique<std::thread>([n, &obj, &stop]()
                        {
                            int idx = 0;
                            for (; !stop; ++ idx)
                            {
                                obj.Reset(std::make_shared<std::string>(std::to_string(n) + "_" + std::to_string(idx)));
                            }
                        }));
                    }
                    
                    threads.push_back(std::make_unique<std::thread>([&obj, &stop]()
                    {
                        int i = 0;
                        while(!stop)
                        {
                            auto sp = obj.Lock();
                            if (sp)
                                std::cout << (++i) << ", " << *sp << std::endl;
                    
                            std::this_thread::sleep_for(10ms);
                        }
                    }));
                    
                    std::this_thread::sleep_for(5s);
                    stop = true;
                    
                    for (auto &item: threads)
                        item->join();
                }

              По сути, сервисный класс, просто контролирующий (на спинлоке) доступ к std::shared_ptr'у из разных потоков, и не дающий его одновременно отресеттить, и скопировать. Синхронизация сделана на спинлоке из рассчёта на то, что обмен указателями (или щёлканье ссылками) в shared_ptr'е происходят достаточно быстро. Кроме того, всё это полагается на то, что внутри shared_ptr'а - атомики и барьеры по памяти. В идеале, конечно, тут в частности (и в таких задачах вообще) требуется мьютекс, сколько бы он не занимал. Потому что mutex создаёт барьер по памяти, и после анлока все изменённые данные становятся видимы всем. Для спинлоков и подобных конструкций такое, естественно, не гарантируется.
              Почему не weak_ptr. Потому что слабая ссылка - это слабая ссылка. Она не держит объект. При проходу по коллекции (как обсуждалось ранее) вполне можно использовать shared_ptr::use_count для того, чтобы определять дохлые объекты и вычищать их. Ну, если это вообще требуется.

              Добавлено
              Причём, обращу внимание, под лочкой делается только то, что практически атомарно работает с памятью. Тяжёлые операции типа аллокации нового ref_counter'а или удаления объекта делается без лочки. И, опять же, в операциях аллокации достаточно спотыкача на мьютексах. Это просто к вопросу о том, что такого рода оптимизация в месте, где присутствуют аллокации - примерно как мёртвому припарки. По замерам, больше времени уходит на всякие new/delete, чем на что-либо ещё.
              Сообщение отредактировано: Flex Ferrum -
                Цитата Flex Ferrum @
                По сути, сервисный класс, просто контролирующий (на спинлоке) доступ к std::shared_ptr'у из разных потоков, и не дающий его одновременно отресеттить, и скопировать.

                Т.е. ты просто навешал мьютекс поверх shared_ptr. Теперь покажи, как ты его навешаешь поверх weak_ptr. Ещё объясни, зачем нужен дополнительный спинлок, если shared_ptr сам по себе уже спинлок?

                А вот насчёт вот этих штук - std::memory_order_acquire и т.п., можно поподробнее? Это как раз та вещь в которой я ничего не понимаю и так и не смог понять.
                Сообщение отредактировано: Олег М -
                  Кстати, а где конструктор копирования для ObjectHolder?
                    1. Навесил не мьютекс, а спинлок. Это немного разные вещи.
                    2. А зачем что-то делать с weak_ptr, если в этих сценариях он не нужен? Для чего тебе нужен именно слабый указатель?
                    3. Спинлоки и атомики - это принципиально разные вещи. Первые реализуются на базе вторых, но вторые не являются первыми. И в shared_ptr нет спинлоков. Там есть атомики.
                    4. Эти волшебные константы управляют ордерингом операций с памятью в многопоточной среде. Но я сам в них толком не разбираюсь - проще прозу найти подходящую.
                      Цитата Flex Ferrum @
                      1. Навесил не мьютекс, а спинлок. Это немного разные вещи.

                      Чем же они разные, mutual exclusions?

                      Цитата Flex Ferrum @
                      Для чего тебе нужен именно слабый указатель?

                      Как минимум, потому что он есть. И нужно гарантировать, чтоб он тоже корректно отрабатывал. Это ссылка на ресурс, которая знает, удалён ресурс или нет. Очень полезная вещь.

                      Цитата Flex Ferrum @
                      3. Спинлоки и атомики - это принципиально разные вещи. Первые реализуются на базе вторых, но вторые не являются первыми. И в shared_ptr нет спинлоков. Там есть атомики.


                      Ну да, совсем разные. Спинлок - это цикл, атомик - это блокировка. Увеличение счётчика в цикле - это не спинлок? Посмотри как реализован shared_ptr у майкрософта. Т.е. вместо того, чтоб навешивать внешний мьютекс, достаточно сделать, чтоб счётчик увеличивался в цикле. Ну и удаление счётчика синхронизировалось (кстати, это как раз то, что отсутствует у майкрософта).

                      Цитата Flex Ferrum @
                      4. Эти волшебные константы управляют ордерингом операций с памятью в многопоточной среде. Но я сам в них толком не разбираюсь - проще прозу найти подходящую.

                      Стоило бы поднять этот вопрос. И разъяснить проблему в более доступных терминах, например в терминах мьютексов. Я прочёл массу статей и ничего не понял. Думаю, я не один такой.
                        Цитата Олег М @
                        Чем же они разные, mutual exclusions?

                        Тем, что мьютекс (в традиционном понимании) - это объект ядра. Даже виндовая критическая секция, которая вся из себя user space, всё равно может свалиться в ядро при длительном ожидании. В случае спинлока - это чисто цикл на уровне user space с маленьким футпринтом как по памяти, так и по аффекту ан перформанс. Ну, при адекватном использовании.

                        Цитата Олег М @
                        Как минимум, потому что он есть. И нужно гарантировать, чтоб он тоже корректно отрабатывал. Это ссылка на ресурс, которая знает, удалён ресурс или нет. Очень полезная вещь.

                        Не. Это "как минимум" в данном случае не работает. У тебя есть сценарии, связанные с таким вот управлением ресурсами, где тебе нужен weak_ptr? Т. е. именно слабая ссылка на указатель, отдельно хранящаяся и требующая инициализации? Нет? Ну так о чём тогда речь? :) Ещё раз: не нужно смешивать контракты и семантику, и не нужно делать вещи, которые можно не делать.

                        Цитата Олег М @
                        атомик - это блокировка

                        Атомик - это не блокировка. Атомик - это атомарная операция с памятью, предоставляющая некоторые гарантии, которые не предоставляют обычные операции. Поэтому увеличение счётчика в цикле - это не спинлок. Это просто увеличение счётчика в цикле таким образом, чтобы другие потоки это видели. Не надо путать тёплое с мягким.

                        Цитата Олег М @
                        Стоило бы поднять этот вопрос. И разъяснить проблему в более доступных терминах, например в терминах мьютексов. Я прочёл массу статей и ничего не понял. Думаю, я не один такой.

                        В терминах мьютексах это не объяснишь. Тут надо понимать, как работает память в многопоточных и многопроцессорных средах.

                        Цитата Олег М @
                        Кстати, а где конструктор копирования для ObjectHolder?

                        Компилятор не маленький, сам напишет. :)
                          Цитата Flex Ferrum @
                          Не. Это "как минимум" в данном случае не работает. У тебя есть сценарии, связанные с таким вот управлением ресурсами, где тебе нужен weak_ptr? Т. е. именно слабая ссылка на указатель, отдельно хранящаяся и требующая инициализации? Нет? Ну так о чём тогда речь? Ещё раз: не нужно смешивать контракты и семантику, и не нужно делать вещи, которые можно не делать.

                          А зачем, по-твоему, вообще нужен weak_ptr?


                          Цитата Flex Ferrum @
                          Компилятор не маленький, сам напишет.

                          Напишет копирование твоего SpinLock? Я бы ему это не доверил.

                          Добавлено
                          Кстати, для реализации класса SpinLock, я бы воспользовался std::atomic_flag
                            Цитата Олег М @
                            А зачем, по-твоему, вообще нужен weak_ptr?

                            Он нужен, чтобы держать "слабую" ссылку на указатель и более точно фиксировать точки владения и точки возможного использования объекта. Типичный кейс - для исключения кольцевых "сильных" ссылок, возникающих в иерархических структурах. В тех случаях, которые мы тут обсуждали, weak_ptr не нужен.

                            Цитата Олег М @
                            Напишет копирование твоего SpinLock? Я бы ему это не доверил.

                            Уверен, он прекрасно справится. Rule of Zero рулит. Но тут его, к сожалению, сложно применить.

                            Добавлено
                            Цитата Олег М @
                            Кстати, для реализации класса SpinLock, я бы воспользовался std::atomic_flag

                            Воспользуйся. Я скопипастил реализацию из документации к бусту. :)
                              Цитата Flex Ferrum @
                              Он нужен, чтобы держать "слабую" ссылку на указатель и более точно фиксировать точки владения и точки возможного использования объекта. Типичный кейс - для исключения кольцевых "сильных" ссылок, возникающих в иерархических структурах. В тех случаях, которые мы тут обсуждали, weak_ptr не нужен.

                              Держать "слабую ссылку" можно разными способами. Простейший - вообще не ссылаться, либо обычная ссылка. WeakPtr же нужен когда ты должен знать - жив тот на кого ты ссылаешься или нет.
                              Вообще, основная проблема с weak_ptr в том, что для него очень сложно обеспечить внешнюю синхронизацию. Однако, если перенести эту синхронизацию внутрь этих классов, что я и сделал, то проблема уходит.

                              Цитата Flex Ferrum @
                              Уверен, он прекрасно справится. Rule of Zero рулит. Но тут его, к сожалению, сложно применить.

                              Он у тебя скопирует заблокированный объект, будет дедлок на ровном месте.
                              Сообщение отредактировано: Олег М -
                                Цитата Олег М @
                                Держать "слабую ссылку" можно разными способами. Простейший - вообще не ссылаться, либо обычная ссылка. WeakPtr же нужен когда ты должен знать - жив тот на кого ты ссылаешься или нет.

                                Интересное мнение. Я привык применять weak_ptr в тех местах, где мне нужно ссылаться на shared_ptr, но копия самого shared_ptr не нужна или опасна (по тем или иным причинам). Если я могу другими способами определить - жив объкт или мёртв, то зачем мне weak_ptr? Для красоты? Для красоты он мне не нужен.

                                Цитата Олег М @
                                Он у тебя скопирует заблокированный объект, будет дедлок на ровном месте.

                                Ну да. Такое действительно возможно. :)
                                1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0605 ]   [ 15 queries used ]   [ Generated: 12.05.24, 03:55 GMT ]