Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.116.51.117] |
|
Страницы: (16) « Первая ... 11 12 [13] 14 15 ... Последняя » все ( Перейти к последнему сообщению ) |
Сообщ.
#181
,
|
|
|
Ну да. Что в этом плохого? Имея ссылку на CSharedPtr, ты можешь в любом потоке сделать копию, которая будет гарантировать, что объект, на который тот указывает, не удалится, либо уже был удалён.
|
Сообщ.
#182
,
|
|
|
Цитата Олег М @ Ну да. Что в этом плохого? Плохого в этом именно вот это и есть: Цитата Олег М @ Имея ссылку на CSharedPtr, ты можешь в любом потоке сделать копию, которая будет гарантировать, что объект, на который тот указывает, не удалится, либо уже был удалён. Ты совмещаешь два контракта, скрещиваешь ежа с ужом. С одной стороны, ты хочешь, чтобы время жизни контролировалось экструзивно, с другой - чтобы изменения, сделанные в одном потоке, были доступны в другом. И указатель себя при этом прекрасно чувствовал. И вот мне не совсем ясно - зачем тебе это именно в таком виде требуется? Что мешает честно разделить контракты? |
Сообщ.
#183
,
|
|
|
Я так и не понял - почему нельзя передавать в другой поток shared_ptr тупо по значению?
|
Сообщ.
#184
,
|
|
|
Цитата OpenGL @ Я так и не понял - почему нельзя передавать в другой поток shared_ptr тупо по значению? Потому что тогда изменения этого указателя в одном потоке не зааффектят другой. А именно это требуется. |
Сообщ.
#185
,
|
|
|
Вспомнил ещё одну задачу, которую я решаю при помощи shared_ptr:
Есть объект, достаточно сложный, который постоянно перезаписывается в одном потоке. И есть несколько потоков, которые читают этот объект, причём операция чтения достаточно долгая - форматирование и всё такое. Чтобы чтение не блокировало запись, я в потоке который пишет создаю новый объект и просто подменяю указатель. Вот как то так: 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(); } }; |
Сообщ.
#186
,
|
|
|
В общем, возьмись я делать что-то подобное, я бы наколходил что-то типа этого:
#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, чем на что-либо ещё. |
Сообщ.
#187
,
|
|
|
Цитата Flex Ferrum @ По сути, сервисный класс, просто контролирующий (на спинлоке) доступ к std::shared_ptr'у из разных потоков, и не дающий его одновременно отресеттить, и скопировать. Т.е. ты просто навешал мьютекс поверх shared_ptr. Теперь покажи, как ты его навешаешь поверх weak_ptr. Ещё объясни, зачем нужен дополнительный спинлок, если shared_ptr сам по себе уже спинлок? А вот насчёт вот этих штук - std::memory_order_acquire и т.п., можно поподробнее? Это как раз та вещь в которой я ничего не понимаю и так и не смог понять. |
Сообщ.
#188
,
|
|
|
Кстати, а где конструктор копирования для ObjectHolder?
|
Сообщ.
#189
,
|
|
|
1. Навесил не мьютекс, а спинлок. Это немного разные вещи.
2. А зачем что-то делать с weak_ptr, если в этих сценариях он не нужен? Для чего тебе нужен именно слабый указатель? 3. Спинлоки и атомики - это принципиально разные вещи. Первые реализуются на базе вторых, но вторые не являются первыми. И в shared_ptr нет спинлоков. Там есть атомики. 4. Эти волшебные константы управляют ордерингом операций с памятью в многопоточной среде. Но я сам в них толком не разбираюсь - проще прозу найти подходящую. |
Сообщ.
#190
,
|
|
|
Цитата Flex Ferrum @ 1. Навесил не мьютекс, а спинлок. Это немного разные вещи. Чем же они разные, mutual exclusions? Цитата Flex Ferrum @ Для чего тебе нужен именно слабый указатель? Как минимум, потому что он есть. И нужно гарантировать, чтоб он тоже корректно отрабатывал. Это ссылка на ресурс, которая знает, удалён ресурс или нет. Очень полезная вещь. Цитата Flex Ferrum @ 3. Спинлоки и атомики - это принципиально разные вещи. Первые реализуются на базе вторых, но вторые не являются первыми. И в shared_ptr нет спинлоков. Там есть атомики. Ну да, совсем разные. Спинлок - это цикл, атомик - это блокировка. Увеличение счётчика в цикле - это не спинлок? Посмотри как реализован shared_ptr у майкрософта. Т.е. вместо того, чтоб навешивать внешний мьютекс, достаточно сделать, чтоб счётчик увеличивался в цикле. Ну и удаление счётчика синхронизировалось (кстати, это как раз то, что отсутствует у майкрософта). Цитата Flex Ferrum @ 4. Эти волшебные константы управляют ордерингом операций с памятью в многопоточной среде. Но я сам в них толком не разбираюсь - проще прозу найти подходящую. Стоило бы поднять этот вопрос. И разъяснить проблему в более доступных терминах, например в терминах мьютексов. Я прочёл массу статей и ничего не понял. Думаю, я не один такой. |
Сообщ.
#191
,
|
|
|
Цитата Олег М @ Чем же они разные, mutual exclusions? Тем, что мьютекс (в традиционном понимании) - это объект ядра. Даже виндовая критическая секция, которая вся из себя user space, всё равно может свалиться в ядро при длительном ожидании. В случае спинлока - это чисто цикл на уровне user space с маленьким футпринтом как по памяти, так и по аффекту ан перформанс. Ну, при адекватном использовании. Цитата Олег М @ Как минимум, потому что он есть. И нужно гарантировать, чтоб он тоже корректно отрабатывал. Это ссылка на ресурс, которая знает, удалён ресурс или нет. Очень полезная вещь. Не. Это "как минимум" в данном случае не работает. У тебя есть сценарии, связанные с таким вот управлением ресурсами, где тебе нужен weak_ptr? Т. е. именно слабая ссылка на указатель, отдельно хранящаяся и требующая инициализации? Нет? Ну так о чём тогда речь? Ещё раз: не нужно смешивать контракты и семантику, и не нужно делать вещи, которые можно не делать. Цитата Олег М @ атомик - это блокировка Атомик - это не блокировка. Атомик - это атомарная операция с памятью, предоставляющая некоторые гарантии, которые не предоставляют обычные операции. Поэтому увеличение счётчика в цикле - это не спинлок. Это просто увеличение счётчика в цикле таким образом, чтобы другие потоки это видели. Не надо путать тёплое с мягким. Цитата Олег М @ Стоило бы поднять этот вопрос. И разъяснить проблему в более доступных терминах, например в терминах мьютексов. Я прочёл массу статей и ничего не понял. Думаю, я не один такой. В терминах мьютексах это не объяснишь. Тут надо понимать, как работает память в многопоточных и многопроцессорных средах. Цитата Олег М @ Кстати, а где конструктор копирования для ObjectHolder? Компилятор не маленький, сам напишет. |
Сообщ.
#192
,
|
|
|
Цитата Flex Ferrum @ Не. Это "как минимум" в данном случае не работает. У тебя есть сценарии, связанные с таким вот управлением ресурсами, где тебе нужен weak_ptr? Т. е. именно слабая ссылка на указатель, отдельно хранящаяся и требующая инициализации? Нет? Ну так о чём тогда речь? Ещё раз: не нужно смешивать контракты и семантику, и не нужно делать вещи, которые можно не делать. А зачем, по-твоему, вообще нужен weak_ptr? Цитата Flex Ferrum @ Компилятор не маленький, сам напишет. Напишет копирование твоего SpinLock? Я бы ему это не доверил. Добавлено Кстати, для реализации класса SpinLock, я бы воспользовался std::atomic_flag |
Сообщ.
#193
,
|
|
|
Цитата Олег М @ А зачем, по-твоему, вообще нужен weak_ptr? Он нужен, чтобы держать "слабую" ссылку на указатель и более точно фиксировать точки владения и точки возможного использования объекта. Типичный кейс - для исключения кольцевых "сильных" ссылок, возникающих в иерархических структурах. В тех случаях, которые мы тут обсуждали, weak_ptr не нужен. Цитата Олег М @ Напишет копирование твоего SpinLock? Я бы ему это не доверил. Уверен, он прекрасно справится. Rule of Zero рулит. Но тут его, к сожалению, сложно применить. Добавлено Цитата Олег М @ Кстати, для реализации класса SpinLock, я бы воспользовался std::atomic_flag Воспользуйся. Я скопипастил реализацию из документации к бусту. |
Сообщ.
#194
,
|
|
|
Цитата Flex Ferrum @ Он нужен, чтобы держать "слабую" ссылку на указатель и более точно фиксировать точки владения и точки возможного использования объекта. Типичный кейс - для исключения кольцевых "сильных" ссылок, возникающих в иерархических структурах. В тех случаях, которые мы тут обсуждали, weak_ptr не нужен. Держать "слабую ссылку" можно разными способами. Простейший - вообще не ссылаться, либо обычная ссылка. WeakPtr же нужен когда ты должен знать - жив тот на кого ты ссылаешься или нет. Вообще, основная проблема с weak_ptr в том, что для него очень сложно обеспечить внешнюю синхронизацию. Однако, если перенести эту синхронизацию внутрь этих классов, что я и сделал, то проблема уходит. Цитата Flex Ferrum @ Уверен, он прекрасно справится. Rule of Zero рулит. Но тут его, к сожалению, сложно применить. Он у тебя скопирует заблокированный объект, будет дедлок на ровном месте. |
Сообщ.
#195
,
|
|
|
Цитата Олег М @ Держать "слабую ссылку" можно разными способами. Простейший - вообще не ссылаться, либо обычная ссылка. WeakPtr же нужен когда ты должен знать - жив тот на кого ты ссылаешься или нет. Интересное мнение. Я привык применять weak_ptr в тех местах, где мне нужно ссылаться на shared_ptr, но копия самого shared_ptr не нужна или опасна (по тем или иным причинам). Если я могу другими способами определить - жив объкт или мёртв, то зачем мне weak_ptr? Для красоты? Для красоты он мне не нужен. Цитата Олег М @ Он у тебя скопирует заблокированный объект, будет дедлок на ровном месте. Ну да. Такое действительно возможно. |