Название: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 24, 2011, 03:48 Вечер добрый)
Хочу спросить у бывалых в многопоточном программировании совета) Ситуация следующая: Имеется один поток, который пишет данные (в некотурую структуру) и есть несколько потоков, которые эти данные читают. Сами данные представляются в виде класса и ссылка на объект этого класса передаётся пишущему и читающим потокам. Ясно, что операция записи в общем случае не атомарна и могут возникнуть неприятности если один или более читающих потоков будут считывать даные, одновременно с записью их пишущим потоком. Использовать mutex тоже не очень рационально, поскольку доступ к данным для чтения будет доступен только одному потоку. Написать класс, который должен эту проблему решить, но есть сомнения... Вот сам класс: Код
Суть в том, что операция с char - атомарны (на сколько мне известно) и перед тем как считать или записать данные мы взводим соответствующий флаг m_flag. Значения: m_flag = 0 - никто не пишет, никто не читает; m_flag = 1 - кто-то пишет (читать запрещено). m_flag = 2 - кто-то читает (запись запрещена, читать можно). примечание: mutable - нужно чтоб метод get() можно было сделать const; volatile - чтоб компилятор не оптимизировал m_flag. Собственно вопрос: По фен-шую ли такое решение или есть грабли? Спасибо за внимание) P.S. пишу ядро и не хотелось бы завязывать его на сторонних библиотеках, Qt использую для ГУЯ только. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: SimpleSunny от Март 24, 2011, 10:38 Сомнения оправданы (:
Посмотрите в сторону QReadWriteLock или погуглите lock-free алгоритмы (http://www.1024cores.net/ http://www.rsdn.ru/forum/cpp/1800649.flat.aspx). Сценарий. Один поток исполняет инструкцию while (m_flag) {}, другой поток исполняет инструкцию while (m_flag & 1) {}, только потом оба потока устанавливают флаг, дальше непредвиденные последствия. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 24, 2011, 11:34 Код :) На эти грабли будут наступать все и всегда, так что не расстраивайтесь :) Дело в том что когда управление вернулось из while - все вроде Ок, m_flag равен нулю. Но беда в том что др. нитка может установить m_flag ПОСЛЕ while но ДО следующей команды (здесь m_flag = 1). Набор подобных ситуаций обсуждался http://www.prog.org.ru/index.php?topic=14448.msg94325#msg94325 (http://www.prog.org.ru/index.php?topic=14448.msg94325#msg94325) Варианты 1) Сделать все самому. В нативных вызовах ничего особо страшного нет (OSSpinLockLock и InterlockedExchange для моих платформ, неплохо и QAtomicInt), но пройдя примерно половину пути приходишь к выводу - нерационально. Вот напр. у Вас пустое тело while. Эта, на первый взгляд несущественная мелочь, начинает доставлять неприятности - одно из ядер "загружено до упора" холостым ходом. Попытки решить это "сходу" (yield и.т.п) вызывают новые проблемы. 2) Использовать Qt QReadWriteLock. Здесь все прекрасно в том смысле что все решается очень быстро после чтения Assistant. Только одно маленькое "но" - Qt честно усыпляет нитки, поэтому при достаточно интенсивных блокировках скорость будет убита в ноль (N ядер медленнее одного). 3) После долгого обдумывания я выбрал Intel TBB и весьма доволен - шикарный выбор локов, atomic и scoped_lock. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 24, 2011, 15:06 Сомнения оправданы (: Да, Вы правы, так и есть.Посмотрите в сторону QReadWriteLock или погуглите lock-free алгоритмы (http://www.1024cores.net/ http://www.rsdn.ru/forum/cpp/1800649.flat.aspx). Сценарий. Один поток исполняет инструкцию while (m_flag) {}, другой поток исполняет инструкцию while (m_flag & 1) {}, только потом оба потока устанавливают флаг, дальше непредвиденные последствия. Цитировать На эти грабли будут наступать все и всегда, так что не расстраивайтесь Улыбающийся Вот вот, я уже понял как наступить на эти грабли)Дело в том что когда управление вернулось из while - все вроде Ок, m_flag равен нулю. Но беда в том что др. нитка может установить m_flag ПОСЛЕ while но ДО следующей команды (здесь m_flag = 1). Набор подобных ситуаций обсуждался http://www.prog.org.ru/index.php?topic=14448.msg94325#msg94325 Вобщем сейчас пытаюсь разобраться с устройством lock-free механизма) Спасибо за обсуждение, я ещё отпишусь, как возникнут вопросы) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 24, 2011, 21:16 Цитировать Варианты А что Вы можете сказать о библиотеки ZThread? 1) Сделать все самому. В нативных вызовах ничего особо страшного нет (OSSpinLockLock и InterlockedExchange для моих платформ, неплохо и QAtomicInt), но пройдя примерно половину пути приходишь к выводу - нерационально. Вот напр. у Вас пустое тело while. Эта, на первый взгляд несущественная мелочь, начинает доставлять неприятности - одно из ядер "загружено до упора" холостым ходом. Попытки решить это "сходу" (yield и.т.п) вызывают новые проблемы. 2) Использовать Qt QReadWriteLock. Здесь все прекрасно в том смысле что все решается очень быстро после чтения Assistant. Только одно маленькое "но" - Qt честно усыпляет нитки, поэтому при достаточно интенсивных блокировках скорость будет убита в ноль (N ядер медленнее одного). 3) После долгого обдумывания я выбрал Intel TBB и весьма доволен - шикарный выбор локов, atomic и scoped_lock. Судя по отзывам она неплоха) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 24, 2011, 21:47 А что Вы можете сказать о библиотеки ZThread? Могу сказать немного - бегло рассматривал как возможный вариант. Не понравился из-за (насколько я помню, могу напутать)Судя по отзывам она неплоха) - выглядит старой (неск лет не обновлялась) - масса template, вникнуть трудно - с локами бедненько (как и с ожиданием) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 25, 2011, 03:47 У меня ещё такой вопрос возник:
Является ли следующая операция атомарна? (m_flag - это char): Код
Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 25, 2011, 09:20 У меня ещё такой вопрос возник: Является ли следующая операция атомарна? (m_flag - это char): Код
Из обычных операций атомарны только запись и чтение, и только того, что не больше size_t. Операция m_flag++ это по сути: R = m_flag; R = R + 1; m_flag = R; где R это регистр процессора. Между строкой 1 и 3 может вклиниться другой тред и всё испортить. Атомарные операции это специальные инструкции процессора (не портабильные), самая главная атомарная операция это atomic CAS, ещё можно почитать про load linked/store conditional. В gcc есть набор встроенных операций типа на __sync*. Например __sync_fetch_and_add (&m_flag, 1). Но эти же операции спрятаны в мьютексах. pthread_mutex, вообще не выходит в режим ядра, если тред успел залочиться, разлочиться, и в середине никто этот мьютекс не захватывал. Поэтому rw_lock часто быстрее любых lockfree (по сути он распадается на atomic++ и atomic--). В данной задаче рекомендация однозначно rwlock. Общее замечание: локфри скорее можно рекомендовать в плане дзена, чем решения конкретной задачи. Мне, когда это изучал, была сама идея интересна. По сути локфри: на локфри операциях можно сделать всё (есть теорема даже :)), но на практике это нечитабельно, плюс бывает, что от локфри тормозов больше, чем скорости, по конкретной задаче надо мерить. Например: как-то наблюдал написание локфри аллокатора, из-за проблем с фрагментацией обычного маллока. Так вот, фрагментировался он меньше, а работал чуть медленнее, хотя в гцц-шном маллоке есть мьютексы. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 25, 2011, 09:52 Код
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 25, 2011, 11:31 Сомнения оправданы (: Посмотрите в сторону QReadWriteLock или погуглите lock-free алгоритмы (http://www.1024cores.net/ http://www.rsdn.ru/forum/cpp/1800649.flat.aspx). Отдельное спасибо хотел бы сказать за ссылки на lock-free. Кто-нибудь использовал приведенную по ссылке (rsdn) реализацию? Имею ввиду fire::store. Извините за оффтоп. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 25, 2011, 14:35 lock-free конечно дело заводное, поражает красота/неожиданность решений и все такое. Но времени оно съедает безумно и "обобщить" ничего не удается, новая задача/потребность - и начинай сначала. А тот локер/мутекс воткнул - и все дела.
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 26, 2011, 08:28 Я почему обратил внимание.
Раньше я просто делал сласс Data потокобезопасным при помощи мьютексов. Сейчас думаю использовать такой подход (псевдокод): Код: class Data {} Как я понял lock-free storage делает то же самое - атомарный обмен указателей, только меньше оверхед + решение ряда других проблем. Вот я и думаю стоит ли заморачиваться с технологией lock-free ради оверхэда использования мьютексов. Но да, идея lock-free достаточно интересная. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 26, 2011, 10:52 Код: class Data {} Именно так все и делают, за счёт shared_ptr оверхеда здесь мизер. Собственно 2 атомарных операции на захват/отдачу мьютекса + 1 атомарная операция на копирование shared_ptr. В лучшем lockfree решении будет 2 атомарных операции. Только для этого lockfree не оправдано. lockfree незаменимо, если очень хочется сделать код reentarable, например для доступа из юникс-сигналов. Но задачу, где это необходимо, ещё придумать надо. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 26, 2011, 12:11 Код: class Data {} Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 26, 2011, 14:20 Цитировать Именно так все и делают, за счёт shared_ptr оверхеда здесь мизер. Собственно 2 атомарных операции на захват/отдачу мьютекса + 1 атомарная операция на копирование shared_ptr. В лучшем lockfree решении будет 2 атомарных операции. Только для этого lockfree не оправдано. А за счёт чего достигается атомарность копирования shared_ptr? Вот например:Код При копировании, на сколько я понял должно произойти как минимум следующее: ptr - должен уменьшить свой счётчик ссылок и если он окажется 0 удалить сам объект, скопировать указатель на новые данные и опять увеличить счётчик ссылок. Наверное я чегото не допонимаю в этом, почему оно атомарно? увеличение числа ссылок, копирование указателя на объект Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 26, 2011, 16:25 А за счёт чего достигается атомарность копирования shared_ptr? Вот например: Код При копировании, на сколько я понял должно произойти как минимум следующее: ptr - должен уменьшить свой счётчик ссылок и если он окажется 0 удалить сам объект, скопировать указатель на новые данные и опять увеличить счётчик ссылок. Наверное я чегото не допонимаю в этом, почему оно атомарно? Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 26, 2011, 16:46 Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос. :) Попробую я пояснитьКод Вполне возможно что напр после первых 10 итераций цикла др. нитка поставит др. data. В этом случае следующие итерации цикла будут работать уже с новыми данными. Это "атомарно" и будет работать корректно. Однако это совсем не значит что данные будут изменены для того кто их уже получил (до замены) с помощью data() - что получил, с тем и работает Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 26, 2011, 19:36 Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос. :) Попробую я пояснитьКод Вполне возможно что напр после первых 10 итераций цикла др. нитка поставит др. data. В этом случае следующие итерации цикла будут работать уже с новыми данными. Это "атомарно" и будет работать корректно. Однако это совсем не значит что данные будут изменены для того кто их уже получил (до замены) с помощью data() - что получил, с тем и работает А за счёт чего достигается атомарность копирования shared_ptr? Вот например: Код При копировании, на сколько я понял должно произойти как минимум следующее: ptr - должен уменьшить свой счётчик ссылок и если он окажется 0 удалить сам объект, скопировать указатель на новые данные и опять увеличить счётчик ссылок. Наверное я чегото не допонимаю в этом, почему оно атомарно? Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос. А, если является, то это обеспечивается собственным мьютексом в самом shared_ptr? Тогда, если это так, то какой смысл в Read(Write)Locker в классе DataManager? И ещё вопрос: В классе DataManager используется shared_ptr только лишь как оптимизация? Ведь аналогичный эффект получится если просто использовать непосредственно Data? Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 26, 2011, 20:23 Хорошо, тогда получается, что сама по себе операция копиррования и присваивания двух shared_ptr (я иммею в виду без участия DataManager) не является атомарной? да (не является атомарной для любого разумного определения атомарности) Edit: собственно термин "атомарный" допускает трактовки. Атомарная функция или operator= -- это непонятно что, надо ещё определение дать функциональной атомарности. И ещё вопрос: В классе DataManager используется shared_ptr только лишь как оптимизация? Ведь аналогичный эффект получится если просто использовать непосредственно Data? да Как пример, такие DataManager-ы используется для классов хранения сеттингов (настройки программы), они могут быть довольно развесистыми. Рабочий тред пользуется сеттингами, но он 1) не должен их на долго лочить, 2) должен видеть консистентное состояние настроек. Такая конструкция с shared_ptr позволяет ускорить захват сеттингов. Но необходимости в shared_ptr здесь нет, просто он копируется на порядок быстрее, чем какой-нибудь std::map <std::string, std::vector <boost::any> >. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 26, 2011, 20:27 Хорошо, тогда получается, что сама по себе операция копиррования и присваивания двух shared_ptr (я иммею в виду без участия DataManager) не является атомарной? да И ещё вопрос: В классе DataManager используется shared_ptr только лишь как оптимизация? Ведь аналогичный эффект получится если просто использовать непосредственно Data? да Как пример, такие DataManager-ы используется для классов хранения сеттингов (настройки программы), они могут быть довольно развесистыми. Рабочий тред пользуется сеттингами, но он 1. не должен их лочить, 2. должен видеть консистентное состояние настроек. Такая конструкция с shared_ptr позволяет ускорить захват сеттингов и тем самым минимизировать трение в локах. Но необходимости в shared_ptr здесь нет, просто он копируется на порядок быстрее, чем какой-нибудь std::map <std::string, std::vector <boost::any> >. Ясно :) Спасибо) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 27, 2011, 11:42 В дополнение ко всему сказанному
Не понял Вашей задумки: m_data "улетела" (из метода data) и тот кто ее подхватит будет использовать указатель на ее данные. Этот "улетевший" указатель не будет обновлен после setData, в чем тогда смысл? Такой подход можно сравнить с получением текущего снапшота данных. Для многих задач этого будет достаточно. Наверное будет понятнее, если в примере просто поменять название метода: Код: class Data {} Здесь мне видится один недостаток: после получения SharedPointer<Data> у нас будет возможность менять данные по указателю. Но с практической точки зрения работать будет быстрее, чем делать класс Data синхронизированным. Да и обойти это можно в принципе. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 27, 2011, 11:53 Такой подход можно сравнить с получением текущего снапшота данных. Для многих задач этого будет достаточно. Наверное будет понятнее, если в примере просто поменять название метода: Это ясно, но вот "для многих" вызывает сомнения. На мой взгляд число таких задач намного меньше по сравнению с обычной синхронизацией запись/чтениеЗдесь мне видится один недостаток: после получения SharedPointer<Data> у нас будет возможность менять данные по указателю. Но с практической точки зрения работать будет быстрее, чем делать класс Data синхронизированным. Да и обойти это можно в принципе. Частенько "в принципе" просто означает "никогда" :)Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 27, 2011, 13:06 Это ясно, но вот "для многих" вызывает сомнения. На мой взгляд число таких задач намного меньше по сравнению с обычной синхронизацией запись/чтение Да, может и так. Видимо по этому чаще встречается описание примеров именно с обычной синхронизацией запись/чтение.Частенько "в принципе" просто означает "никогда" :) Ну я имел ввиду, что это возможно. Простой пример: Цитировать void SomeClass::foo() Писанины больше, но и скорость выполнения тоже. Иногда это оправдано, иногда - нет.{ SharedPointer<Data> data = cfg.getDataSnapshot(); Data *ptr = data.get(); assert(prt); doStuff(*ptr); } void SomeClass:doStuff(const Data &data) { ... } Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 27, 2011, 15:21 В дополнение ко всему сказанному Не понял Вашей задумки: m_data "улетела" (из метода data) и тот кто ее подхватит будет использовать указатель на ее данные. Этот "улетевший" указатель не будет обновлен после setData, в чем тогда смысл? Такой подход можно сравнить с получением текущего снапшота данных. Для многих задач этого будет достаточно. Наверное будет понятнее, если в примере просто поменять название метода: Код: class Data {} Здесь мне видится один недостаток: после получения SharedPointer<Data> у нас будет возможность менять данные по указателю. Но с практической точки зрения работать будет быстрее, чем делать класс Data синхронизированным. Да и обойти это можно в принципе. Для большей строгости можно использовать const propagation: Код:
Соответственно, у кого const ссылка на DataManager не может изменить состояние данных. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 27, 2011, 18:59 Для большей строгости можно использовать const propagation: ... Соответственно, у кого const ссылка на DataManager не может изменить состояние данных. а почему не сделать так: Код
в чём смысл второго метода с не-конст указателем, если полученный объект нельзя менять всё равно? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 27, 2011, 19:10 Для большей строгости можно использовать const propagation: Да, я перемудрил. Так лучше. А еще можно мьютекс сделать mutable и оставить только константный getDataSnapshot() Код:
brankovic уже подметил Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 27, 2011, 19:51 Для большей строгости можно использовать const propagation: ... Соответственно, у кого const ссылка на DataManager не может изменить состояние данных. а почему не сделать так: Код
в чём смысл второго метода с не-конст указателем, если полученный объект нельзя менять всё равно? почему полученный объект (Data) нельзя менять? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 27, 2011, 20:30 Если привлекается LockRead, то вся конструкция теряет смысл (какой же это lock-free?). Практичнее прикинуть какой read/write локер пользовать.
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 27, 2011, 20:44 почему полученный объект (Data) нельзя менять? Менять Data можно, но его не следует менять, тк. Data будет расшарен между потоками, а синхронизация в нем не реализована. Те Data предназначен (нужен) только для чтения данных, и поэтому надо возвращать указатель на константный объект. И, кстати, const SharedPointer тут не важен, главное чтоб const Data был. Если привлекается LockRead, то вся конструкция теряет смысл (какой же это lock-free?). Практичнее прикинуть какой read/write локер пользовать. Да мы вроде уже не о lock-free. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 27, 2011, 21:07 Цитировать Менять Data можно, но его не следует менять, тк. Data будет расшарен между потоками, а синхронизация в нем не реализована. согласенТе Data предназначен (нужен) только для чтения данных, и поэтому надо возвращать указатель на константный объект. Цитировать И, кстати, const SharedPointer тут не важен, главное чтоб const Data был. Это нужно для явной семантики присваивания. Например, без const можно было бы написать getDataSnapshot().reset(newData) или getDataSnapshot() = somePointer, что совершенно бесполезно, поскольку объект временный. Короче, нет никакого смысла менять временный объект, и для предотвращения подобных действий объект возвращается как константный.Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 27, 2011, 21:11 Интересно было бы напр. такое
Типовая задача: одна нитка кладет элементы (напр задания) в контейнер, N др. ниток их оттуда изымают. Понятно это легко решается через ReadWriteLock, но только до тех пор пока интенсивность записи/чтения относительно невысока, дальше тормоза. (Q)ReadWriteLock - решение прямолинейное (захват всего контейнера). А как сделать по-умному, напр. захватывая лишь "необходимые" элементы? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 01:33 Подправил изначальный вариант AtomicData. Теперь по идеи должно работать коррестно (пока правда только под linux).
Код
Где atomic_inc_and_test - атомарный инкремент и возвращает true если результат равен нулю. Флаги m_flag1 и m_flag2: Если оба равны -1 то никто не читает и никто не пишет (данные свободны) Если m_flag1 == -1 и m_flag2 != -1 то можно читать (более одного потока). Но возникла проблема: Нигде не могу найти у себя хедер atomic.h В каталоге asm его нет :( Где его искать? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 02:51 Вобщем найти этот atomic.h так и не удалось, но нашёл cstdatomic (c++0x) где реализован очень удобный шаблон atomic<T>
Код
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 11:12 Код
Без "идиомы CAS" не обойтись. Просто "присваивание" (напр key1 = -1) почти всегда неверно (хоть и атомарно). И если уж взялись писать свой ReadWriteLocker, то делайте и "write pending" (запрос на запись) чтобы желающие, увидев этот флаг, не захватывали по чтению - иначе запись может не пробиться. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 12:49 Код
Без "идиомы CAS" не обойтись. Просто "присваивание" (напр key1 = -1) почти всегда неверно (хоть и атомарно). И если уж взялись писать свой ReadWriteLocker, то делайте и "write pending" (запрос на запись) чтобы желающие, увидев этот флаг, не захватывали по чтению - иначе запись может не пробиться. Например вначале key1 = -1. После первой проверки с инкрементом: Код
Мы получим res1 = true и key1 = 0; Далее другая нитка делает тоже, но key1 будет уже равен 1 и res вернёт false :) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 28, 2011, 13:57 ... не понял задумки, по этой функции получается, что любой, кто выходит из критической секции её разлочивает. Т.е. 2 треда вошли в секцию, потом один заснул, а другой вышел. Пришёл третий и залочит на запись, SIGSEGV. Я правильно понимаю, что ридеров допускается несколько? Код
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 14:11 Код Пусть res1 и res2 оказались равны true. Запрос на запись получен и выполняется m_data = data, В этот момент др. нитка захотела читать Код Это крутится в while, поэтому на втором витке запрос на чтение также будет успешно получен. Итого: одна читает, др. (в это же время) пишет (кум рулюе, я газую, iдемо) Если Вас не затруднит, не могли бы Вы дать хоть как-то осмысленные имена. Что такое key1, key2.- хз. "test_for" лучше заменить на "acquire", т.к. тестирование не имеет смысла при параллельном выполнении Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 14:25 brankovic , Igors, Вынужден признать, что Вы правы))
Но уверен, решение близко и грех его не найти)) Цитировать Если Вас не затруднит, не могли бы Вы дать хоть как-то осмысленные имена. Что такое key1, key2.- хз. "test_for" лучше заменить на "acquire", т.к. тестирование не имеет смысла при параллельном выполнении Согласен, исправлю)Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 14:51 Но уверен, решение близко и грех его не найти)) 0 - никто не читает и никто не пишет-1 - одна (и только одна) нитка пишет STATE_WRITE > 0 - число читающих Код Здесь нет write_pending - ну чтобы было о чем подумать :) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 28, 2011, 15:16 Пришло в голову вот такое lockfree решение для вышеупомянутого DataManager. Это решение для одного писателя и нескольких читателей (если писателей несколько, то их пришлось бы синхронизировать).
Код: struct Data В коде нет ни одной блокировки! Имеются два присваивания - в прямом и инверсном порядке, что гарантирует консистентность Data для читателей. Чем платим: 1. const Data data() const: одна операция !=, выполняемая всегда; тело цикла (с условием) выполняются только в момент записи писателем. 2. void setData(const Data& value): дополнительное инверсное присваивание. 3. Если класс Data достаточно "тяжел" в плане присваиваний, то эффективность, очевидно, падает. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 15:41 Ну операции сравнения и присваивания для структур могут быть недешевы. Как я понял, в любом случае читатель получит "консистентный снапшот" (пусть не всегда обновленный), но тогда проще обойтись без 2 вариантов assignment, а просто сравнивать старую копию с новой. А вот на первый взгляд простой способ: пусть писатель взводит/сбрасывает флажок mDataValid здесь не пройдет :)
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 28, 2011, 15:51 Цитировать ...а просто сравнивать старую копию с новой... В этом случае читатель может получить неконсистентные данные.Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 28, 2011, 15:51 Код
void setData(const Data& value) тред A дошёл до строки m_data1.directAssignment(value); в void setData(const Data& value); тред B дошёл до строки const Data result = m_data1; в const Data data() const; и они начали одновременно первый писать в m_data1, второй читать из него. В чём тогда синхронизация? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 16:04 В этом случае читатель может получить неконсистентные данные. Прошу показать каким образом.и они начали одновременно первый писать в m_data1, второй читать из него. В чём тогда синхронизация? Да, если напр будет член std::vector, то рухнет. Но если копирование thread-safe (напр то же Qt shallow copy), то это неопасно, будет копироваться еще пока 2 копии не совпадут. В общем тот же CAS (слегка извращенный :))Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 28, 2011, 16:09 Да, если напр будет член std::vector, то рухнет. Но если копирование thread-safe (напр то же Qt shallow copy), то это неопасно Уверен и там не всё складно с этим присваиванием наоборот и operator==, который тоже не атомарен. Просто привёл самый очевидный изъян. Edit: кстати, shallow копирование довольно сложно реализовать полностью thread safe. Вы уверены, что QString, например, не падает при одновременном чтении и присвоении? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 28, 2011, 16:35 В этом случае читатель может получить неконсистентные данные. Прошу показать каким образом.Пусть в структуре (Data) будет два члена и она меняется из состояния {0, 0} в состояние {1, 1}. Сценарий: 1. Писатель выполнил a = other.a; из directAssignment для m_data1; m_data1 = {1, 0}. 2. Читатель выполнил const Data result = m_data1; result = {1, 0}. 3. Писатель завершил первое присваивание (m_data1) и во втором присваивании (из directAssignment, а не из reverseAssignment - ваш вариант) выполнил a = other.a для m_data2; m_data2 = {1, 0}. 4. Читатель проверяет условие while (result != m_data2), и мы имеем result == m_data2 == {1, 0} - неконсистентность! Data должна меняться атомарно для читателей, т.е. либо {0, 0}, либо {1, 1}. Цитировать и они начали одновременно первый писать в m_data1, второй читать из него. В чём тогда синхронизация? Синхронизация в том, что читатель получит корректные данные (независимо от временной внутренней неконсистентности). Структура Data просто определяет атомарную группу, в самой структуре данные-члены понимаются атомарными в смысле присваивания. Прошу прощения, что не указал этого. Название: Re: AtomicData или механизм атомарной операции записl Отправлено: Igors от Март 28, 2011, 16:54 Edit: кстати, shallow копирование довольно сложно реализовать полностью thread safe. Вы уверены, что QString, например, не падает при одновременном чтении и присвоении? Да, они подсовывают указатель на новые данные атомарно. Так что при "чтении и присвоении" все нормально, также и при "записи и присвоении" - но не при "записи и чтении"3. Писатель завершил первое присваивание (m_data1) и во втором присваивании (из directAssignment, а не из reverseAssignment - ваш вариант) выполнил a = other.a для m_data2; m_data2 = {1, 0}. Согласен. Тогда может лучше так4. Читатель проверяет условие while (result != m_data2), и мы имеем result == m_data2 == {1, 0} - неконсистентность! Data должна меняться атомарно для читателей, т.е. либо {0, 0}, либо {1, 1}. Код Чтобы обойтись без утомительного сравнения "задом наперед" Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 28, 2011, 17:28 Структура Data просто определяет атомарную группу, в самой структуре данные-члены понимаются атомарными в смысле присваивания. Прошу прощения, что не указал этого. хорошо, тогда как с этим: data == AA AA W хочет записать BB BB W записал B, data == BA AA R хочет прочесть data R прочёл BA, заснул W закончил, и стал писать AA AA, записал AA A, data == AA BA R проснулся, прочитал BA, в стеке у него BA BA, он их сравнил и остался доволен, но data никогда не принимала значения BA Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 18:33 И так, вроде ничего не упустил)
Код Пояснения: m_counter_readers - число читающих потоков (в данный момент времени) m_state_write - если = -1 - никто не пишет, в противном случае есть ожидающие записи Читать данные могут одновременно несколько потоков, записывать всегда только какой-то один. Если кто-то пишет, читать запрещено. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 18:59 R проснулся, прочитал BA, в стеке у него BA BA, он их сравнил и остался доволен, но data никогда не принимала значения BA Верно, совпадение возможно и при обратном порядке присваиванияКод
Цитировать С Ларисой я действительно пил вино, но это было на пасху Т.е. state_write действительно был -1, но до тех пор пока это присвоится readers, др. нитка успеет захватить по записи. Лучше держать все в одном AtomicInt. с двумя переменными заморочек намного больше. Ваше упорство избежать CAS "заслуживает лучшего применения" :) И как там у Вас с холостым ходом? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 19:12 Код
Цитировать С Ларисой я действительно пил вино, но это было на пасху Т.е. state_write действительно был -1, но до тех пор пока это присвоится readers, др. нитка успеет захватить по записи. Лучше держать все в одном AtomicInt. с двумя переменными заморочек намного больше. Ваше упорство избежать CAS "заслуживает лучшего применения" :) И как там у Вас с холостым ходом? [/quote] Не атомарно? Ну это можно исправить следующим образом: Код
Цитировать И как там у Вас с холостым ходом? В смысле? С ним что-то не так?Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 28, 2011, 19:22 Не атомарно? Ну это можно исправить следующим образом: Опять не атомарно - ведь N ниток могли увеличить readers "между 2-мя строками" и, присваивая единицу, Вы это значение теряетеКод
Цитировать И как там у Вас с холостым ходом? В смысле? С ним что-то не так?Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 28, 2011, 19:32 В том смысле что его вообще пока не видно - тело while отсутствует. Это означает что ядро будет молотить впустую и, как показала жизнь, это не так уж безобидно. это как-то уж слишком коварно.. По смыслу m_ax спинлок хотел, а в спинлоке так и должно быть. Название: Re: AtomicData или механизм атомарной операции записl Отправлено: Igors от Март 28, 2011, 19:49 это как-то уж слишком коварно.. По смыслу m_ax спинлок хотел, а в спинлоке так и должно быть. Не должно. Про свой OSX могу сказать: OSSpinLockLock (спиннер) не просто "крутит while". А TBB сначала просто делает какое-то число nop, затем вставляет pause а потом уж shed_yield. При этом учитывается специфика процессора. В общем, это на порядок сложнее чем то что сейчас обсуждается :) А без этого (увы) могут быть заметные тормоза для др. задач Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 19:57 А что Вы скажите на это ;):
Код
Название: Re: AtomicData или механизм атомарной операции записl Отправлено: m_ax от Март 28, 2011, 20:09 это как-то уж слишком коварно.. По смыслу m_ax спинлок хотел, а в спинлоке так и должно быть. Не должно. Про свой OSX могу сказать: OSSpinLockLock (спиннер) не просто "крутит while". А TBB сначала просто делает какое-то число nop, затем вставляет pause а потом уж shed_yield. При этом учитывается специфика процессора. В общем, это на порядок сложнее чем то что сейчас обсуждается :) А без этого (увы) могут быть заметные тормоза для др. задач Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 28, 2011, 20:23 Короче, окончательный вариант SpinLock:
Код
Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 28, 2011, 21:22 это как-то уж слишком коварно.. По смыслу m_ax спинлок хотел, а в спинлоке так и должно быть. Не должно. Про свой OSX могу сказать: OSSpinLockLock (спиннер) не просто "крутит while". А TBB сначала просто делает какое-то число nop, затем вставляет pause а потом уж shed_yield. При этом учитывается специфика процессора. В общем, это на порядок сложнее чем то что сейчас обсуждается :) А без этого (увы) могут быть заметные тормоза для др. задач а, понятно, думал о wait-free речь пойдёт. Никогда такую оптимизацию в действии не видел. Но интересно, что здесь сложного? Казалось бы раз в n циклов вызывать yeild, и всё? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 28, 2011, 21:59 Короче, окончательный вариант SpinLock: Код
R0 читал R1 дошёл до if, увидел, что кто-то читает, отправился на else R1 заснул R0 ушёл (readers == 0) W пришёл и захватил на запись R1 проснулся, сделал ++readers и вернул true теперь R0 читает, а W пишет Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 28, 2011, 22:01 Цитировать хорошо, тогда как с этим: data == AA AA W хочет записать BB BB W записал B, data == BA AA R хочет прочесть data R прочёл BA, заснул W закончил, и стал писать AA AA, записал AA A, data == AA BA R проснулся, прочитал BA, в стеке у него BA BA, он их сравнил и остался доволен, но data никогда не принимала значения BA Верно! Я рассмотрел синхронизацию в контексте только одного вызова setData, позабыв про возможность 2-х и более вызовов. Спасибо. Навскидку, если добавить отслеживание вызовов (проблему переполнения счетчика пока опустил): Код: struct Data Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 28, 2011, 23:10 Да нет, тоже не пойдет
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 29, 2011, 10:20 Хм.. Ещё один вариант, надеюсь последний)
Код Во всяком случае, ситуация описанная brankovic: Цитировать R0 читал исключена.R1 дошёл до if, увидел, что кто-то читает, отправился на else R1 заснул R0 ушёл (readers == 0) W пришёл и захватил на запись R1 проснулся, сделал ++readers и вернул true теперь R0 читает, а W пишет Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Март 29, 2011, 10:45 Хм.. Ещё один вариант, надеюсь последний) Код
R читает W пришёл и сделал bool tmp = (++state_write == 0); R вышел и установил state_write = -1 W установил state_write = 0 и завис навсегда Название: Re: AtomicData или механизм атомарной операции записl Отправлено: Igors от Март 29, 2011, 10:58 Если удасца реализовать первоночальный SpinLock, то его можно будет ускорить создав буфер даных. Тогда простои при записи и чтении можно будет существенно сократить. Не понял, расскажите подробнееа, понятно, думал о wait-free речь пойдёт. Никогда такую оптимизацию в действии не видел. Но интересно, что здесь сложного? Казалось бы раз в n циклов вызывать yeild, и всё? Пробовал :) Довольно трудно найти баланс, просто yield ощутимо тормозит текущую задачу, просто прокрутка - остальные, причем нужно подстраиваться под конкретную машину. Ну и вся эта песня платформо-зависима.Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 29, 2011, 10:58 Цитировать R читает а мы вот так:W пришёл и сделал bool tmp = (++state_write == 0); R вышел и установил state_write = -1 W установил state_write = 0 и завис навсегда Код :) Название: Re: AtomicData или механизм атомарной операции записl Отправлено: m_ax от Март 29, 2011, 11:10 Если удасца реализовать первоночальный SpinLock, то его можно будет ускорить создав буфер даных. Тогда простои при записи и чтении можно будет существенно сократить. Не понял, расскажите подробнееМне проще код накидать)) Но это когда разберусь со SpinLock ом. Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 29, 2011, 11:49 а мы вот так: Код :) никого не было W вызвал acquireWrite и дошёл до if (tmp && !readers) R вызвал acquiteRead и сделал ++readers W испугался и вернул false теперь state_write == 0 и никогда не станет равным -1, R и W зависли Edit: вообще как на счёт обычного лока (не rw), легко реализовать? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 29, 2011, 11:50 а мы вот так: Так то же самое: перед выполнением state_write = 0; все readers могли отстреляться, имеем deadlock, т.к. никто не может снять state_write с нуля. И как бы Вы ни крутили, без CAS дырка всегда найдется :)Код
В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев. Это интересно обсудить, но выделите пожалуйста в отдельную тему (предполагаем что какой-то ReadWriteLock имеется) Мне проще код накидать)) Но это когда разберусь со SpinLock ом. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: SimpleSunny от Март 29, 2011, 12:02 Это интересно обсудить, но выделите пожалуйста в отдельную тему (предполагаем что какой-то ReadWriteLock имеется) Отдаленно похожая, но тоже интересная, идея обсуждалась тут (http://alenacpp.blogspot.com/2010/07/blog-post_30.html). Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 29, 2011, 12:22 Это интересно обсудить, но выделите пожалуйста в отдельную тему (предполагаем что какой-то ReadWriteLock имеется) Отдаленно похожая, но тоже интересная, идея обсуждалась тут (http://alenacpp.blogspot.com/2010/07/blog-post_30.html). Цитировать Это интересно обсудить, но выделите пожалуйста в отдельную тему (предполагаем что какой-то ReadWriteLock имеется) Хорошо, как освобожусь так напишу в отдельную тему)Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 29, 2011, 18:07 Никак не даёт покоя идея реализации этого злосчастного SpinLock.
Несколько пересмотрел логику и теперь это выглядит так: Код
Краткое описание: Есть две переменные key и state. state может принимать следующие значения: 00 = 0 - STATE_FREE; 01 = 1 - STATE_READ; 10 = 2 - STATE_WRITE; 11 = 3 - STATE_READ_AND_WRITE; (виртуальное состояние) key нужин лишь для того, чтобы отобрать какой либо один поток для записи. Как Вам такой вариант? :) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 29, 2011, 18:56 R0 читает; state == STATE_READ
R1 читает; state == STATE_READ и дочитывает; state == STATE_FREE W0 начинает писать (в то время как R0 все еще читает) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 29, 2011, 19:12 R0 читает; state == STATE_READ Согласен :(R1 читает; state == STATE_READ и дочитывает; state == STATE_FREE W0 начинает писать (в то время как R0 все еще читает) Исправил этот момент, введя счётчик читающих потоков: m_counter. Теперь state = STATE_FREE только когда все читатели перестанут читать: Код Вот так) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 29, 2011, 20:32 m_ax, просьба: давайте по пустому не месить и форум понапрасну не утомлять.
Догнать а что же такое STATE_READ_AND_WRITE - мудрено. Что-то типа "Rat but blue"? :) Код
Я уважаю желание "докопаться самому" и с подозрением отношусь к "начитавшимся классов", но чего ж об один камень 10 раз спотыкаться? :) Зачем упорно избегать CAS который предназначен для решения таких коллизий? Зачем рыть себе яму привлекая вторую переменную за которой надо (мучительно) следить? Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 30, 2011, 00:06 Код
R читал W пришёл и сделал state |= STATE_WRITE, state == STARE_READ_WRITE R закончил чтение, state = STATE_FREE W сделал state = STATE_READ и завис Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 30, 2011, 08:41 Вот, тоже подсел :)
Псевдокод: Код: template <class T> Ограничения: только один писатель, проблема переполнения счетчика опущена. Смысл опрераций: AtomicInt::fetchAndInc AtomicInt::fetchAndDec AtomicInt::fetchAndOr AtomicInt::fetchAndXor думаю всем понятен. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: vregess от Март 30, 2011, 08:46 m_ax, а ты пишешь тесты для своего алгоритма?
Может стоит протестировать реализацию сперва - сразу сократишь количество постов в теме. Да и народ не будет мучиться описывая вероятные исходы. Возьми boost.test или googletest или что-нить еще.. Заодно и производительность сравнить можно будет с обычным rw подходом. м?) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 30, 2011, 09:08 Нужно увеличивать скважность импульса, например, отказаться от остатка кванта
Код: ... Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 30, 2011, 10:14 Смысл опрераций: Без них можно обойтись AtomicInt::fetchAndInc AtomicInt::fetchAndDec AtomicInt::fetchAndOr AtomicInt::fetchAndXor думаю всем понятен. Код Ну CompareAndSwap надо найти в хедере для своей платформы Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 30, 2011, 13:11 m_ax, просьба: давайте по пустому не месить и форум понапрасну не утомлять. Да, Вы правы. Пора завязывать с этой темой)Догнать а что же такое STATE_READ_AND_WRITE - мудрено. Что-то типа "Rat but blue"? :) Код
Я уважаю желание "докопаться самому" и с подозрением отношусь к "начитавшимся классов", но чего ж об один камень 10 раз спотыкаться? :) Зачем упорно избегать CAS который предназначен для решения таких коллизий? Зачем рыть себе яму привлекая вторую переменную за которой надо (мучительно) следить? Всем спасибо за обсуждение) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Akon от Март 30, 2011, 16:20 2 Igors:
Вроде рабочий вариант с многими писателями ;) Функции: AtomicInt::fetchAndInc AtomicInt::fetchAndDec AtomicInt::fetchAndOr AtomicInt::fetchAndXor могут быть реализованы через CompareAndSwap(), где последняя крутится в цикле до тех пор, пока не установит свое значение (lock-free), но их естественная реализация через блокировку шины, типа [lock xadd dword ptr [ecx],eax] (wait-free). CompareAndSwap: Если что, то этот функционал есть в QAtomicInt: bool QAtomicInt::testAndSetXXX(int expectedValue, int newValue); Мне в setData как раз нужно было использовать CompareAndSwap. Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 30, 2011, 17:32 2 Igors: 1) Ну "только один писатель" вполне usable, а вот без pending (запрос на запись) прожить трудно. Вроде рабочий вариант с многими писателями ;) Функции: AtomicInt::fetchAndInc AtomicInt::fetchAndDec AtomicInt::fetchAndOr AtomicInt::fetchAndXor могут быть реализованы через CompareAndSwap(), где последняя крутится в цикле до тех пор, пока не установит свое значение (lock-free), но их естественная реализация через блокировку шины, типа [lock xadd dword ptr [ecx],eax] (wait-free). CompareAndSwap: Если что, то этот функционал есть в QAtomicInt: bool QAtomicInt::testAndSetXXX(int expectedValue, int newValue); Мне в setData как раз нужно было использовать CompareAndSwap. 2) CompareAnSwap умышленно "псевдокод", выгоднее собрать платформо-зависимые вызовы в один (компактный) файл, который легко приспособить с Qt или без - по обстановке. Кстати AtomicInt не требуется, можно просто int 3) Само собой все это надо оформить в класс и добавить метод типа DoNothing() - холостой ход. Плюс класс для "scoped" При всем этом: относительно недавно столкнулся с ситуацией когда даже этот локер (увы) тормозит - уж очень велика интенсивность :) Название: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Март 31, 2011, 01:01 Нужно увеличивать скважность импульса, например, отказаться от остатка кванта :o кажется я понимаю! ..да нет, кого я обманываю.. ;DНо интересный вариант Вы описали, правда смущает ограничение на одного пишущего и завис писателя, если поток читателей постоянный. Но в реализации подкопаться не к чему (кроме опечатки ;). А работает это потому, что каждая атомарная переменная используется только в атомарных операциях, а m_ax всё время компрометировал атомарность присвоением. Если отказаться от присвоений, то задача становится проще, потому что огород городить сложнее. 1) Ну "только один писатель" вполне usable, а вот без pending (запрос на запись) прожить трудно. Кстати, зачем write_pending, почему нельзя просто ставить write (как у Akon, только CAS вместо fetchAndXor)? Ну то есть чтобы читатели не подвесили писателя, как я понял понял, но вроде бы одного write достаточно и короче получится, нет? Собственно с CAS это другой класс задач уже, тут можно уже делать wait-free, не то что спинлоки (кстати, CAS сам по себе такое же wait-free, как и атомарный ++). Теперь по сути задачи, которую решал m_ax: как сделать рв-спинлок без CAS? Как известно в математике и некоторых дугих религиях, задача станет ещё проще, если отказаться от задачи. А именно, реализовать обычный, а не рв-лок, например так: Код
понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать 8) Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Март 31, 2011, 07:08 Кстати, зачем write_pending, почему нельзя просто ставить write (как у Akon, только CAS вместо fetchAndXor)? Ну то есть чтобы читатели не подвесили писателя, как я понял понял, но вроде бы одного write достаточно и короче получится, нет? Писателя не повесят, но могут сильно затормозить (часто читающих заметно больше). Хотя может я не совсем Вас понял.Код
Код
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 31, 2011, 11:03 Нужно увеличивать скважность импульса, например, отказаться от остатка кванта :o кажется я понимаю! ..да нет, кого я обманываю.. ;DНо интересный вариант Вы описали, правда смущает ограничение на одного пишущего и завис писателя, если поток читателей постоянный. Но в реализации подкопаться не к чему (кроме опечатки ;). А работает это потому, что каждая атомарная переменная используется только в атомарных операциях, а m_ax всё время компрометировал атомарность присвоением. Если отказаться от присвоений, то задача становится проще, потому что огород городить сложнее. 1) Ну "только один писатель" вполне usable, а вот без pending (запрос на запись) прожить трудно. Кстати, зачем write_pending, почему нельзя просто ставить write (как у Akon, только CAS вместо fetchAndXor)? Ну то есть чтобы читатели не подвесили писателя, как я понял понял, но вроде бы одного write достаточно и короче получится, нет? Собственно с CAS это другой класс задач уже, тут можно уже делать wait-free, не то что спинлоки (кстати, CAS сам по себе такое же wait-free, как и атомарный ++). Теперь по сути задачи, которую решал m_ax: как сделать рв-спинлок без CAS? Как известно в математике и некоторых дугих религиях, задача станет ещё проще, если отказаться от задачи. А именно, реализовать обычный, а не рв-лок, например так: Код
понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать 8) Первый поток вызывает lock (n = 1, level = 1), но до unlock ещё не успевает дойти. В этот момент другой поток вызывает lock (n = 2, level = 1). Соответственно он не проходит и крутится в цикле. Затем, первый поток вызывает unlock (level = 2+1 = 3), что не освобождает второй поток, который продолжает крутится в цикле. Лучше изменить unlock так: Код
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Март 31, 2011, 14:17 Цитировать понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать Крутой Хорошо, предположим, что у нас есть такой мьютекс (реализованный без CAS) И как с помощью него сделать lock-free?Вот пример самого мьютекса: Код
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Апрель 01, 2011, 00:17 А в функции unlock, nn - это что? это опечатался. На самом деле level++, никакого nn нет. Igors, счётчик врапится бесконечно, важно, чтобы число тредов одновременно ждущих мьютекса не превысило диапазон счётчика. Понятно, что 4e9 тредов одновременно работать не могут. Edit: не совсем понял про "махровый семафор", у меня всегда 1 захвативший, т.е. это мьютекс. Что CAS лучше, то тут спорить не о чем, конечно он и эффективнее и гибче. Просто интересно было обойтись ++, тем более, что спинлок несложный примитив. Правда эффективной реализации не получилось. m_ax имея обычный мьютекс можно реализовать rw-мьютекс так: Код
Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность: T1, T2 пришли, захватили ключи 0 и 1, key = 1 T1 отработал и ушёл, key = -1 T2 решил сделать --key, и завис навсегда (между key = -1 и -2) Название: Re: AtomicData или механизм атомарной операции записl Отправлено: Igors от Апрель 01, 2011, 10:05 Код
Мне кажется тема зашла в тупик, на мой взгляд гораздо лучше/полезнее было бы обсудить вот это В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев. Конечно в новой темеНазвание: Re: AtomicData или механизм атомарной операции записl Отправлено: brankovic от Апрель 01, 2011, 11:55 3 нитки сделали lock(), n = 3. level = 1. Одна из ниток проскочила, 2 остальных крутят while. Затем проскочившая вызывает unlock(), но это не разблокирует мутекс (n = 3. level = 2). 2й тред будет иметь в стеке nn == 2 и получит право на лок, как только level станет 2 Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Апрель 01, 2011, 12:00 Цитировать Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность: Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0T1, T2 пришли, захватили ключи 0 и 1, key = 1 T1 отработал и ушёл, key = -1 T2 решил сделать --key, и завис навсегда (между key = -1 и -2) Цитировать lock_read () А что такое scoped_lock ? И как он устроен?{ while (true) { scoped_lock sl (m_mutex); if (!m_writer) { ++m_readers; return; } } } Название: Re: AtomicData или механизм атомарной операции записl Отправлено: m_ax от Апрель 01, 2011, 12:03 Мне кажется тема зашла в тупик, на мой взгляд гораздо лучше/полезнее было бы обсудить вот это В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев. Конечно в новой темеНазвание: Re: AtomicData или механизм атомарной операции записl Отправлено: Igors от Апрель 01, 2011, 12:17 2й тред будет иметь в стеке nn == 2 и получит право на лок, как только level станет 2 Вы правы :)А что такое scoped_lock ? И как он устроен? хороший пример QMutexLocker (пасется на деструкторе, поэтому scoped) И ещё для этого, если использовать только абстрактные rwlocker нужно мне бы хотелось знать логику их работы. Ну об этом было много сказано выше :) "Read-write" - название неточное, строго говоря, "shared-exclusive". То есть захват может быть или многими нитками (shared) или только одной (exclusive). Это может быть чтение-запись, но не только Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0 но ведь возможно что tmp > 0, а key уже -1, тогда после key-- он станет -2 (с печальными последствиями)Давайте создадим, как предлагаете назвать? Ну давайте я создам используя цитату Вашего поста - не возражаете ?Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Апрель 01, 2011, 12:35 хотел написать, но Igors уже всё сказал. Добавлю, что scoped_lock очень важная вещь, в рабочем коде обычный mutex::lock () вообще лучше не использовать, так что рекомендую ознакомиться.
Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Апрель 01, 2011, 12:36 Цитировать Ну давайте я создам используя цитату Вашего поста - не возражаете ? Не возражаю)Цитировать Ну об этом было много сказано выше Улыбающийся "Read-write" - название неточное, строго говоря, "shared-exclusive". То есть захват может быть или многими нитками (shared) или только одной (exclusive). Это может быть чтение-запись, но не только Мне интересно следующее: Пусть писатель W1 пишет данные, приходят ещё несколько писателей. Пока они ждут своей очереди. Теперь приходит читатель R1, читать пока запрещено, пока W1 пишет. Вопрос, когда будет разрешено читать R1? Когда допишет текущий W1 или пока допишут все писатели, что заявили о себе до прихода R1?Аналогичный вопрос, но с ситуацией, когда изначально читали несколько читателей. Приходит писатель. Пока ему писать запрещено: он ждёт. В это время приходят ещё читатели. Какого условие, разрешающее ему начать писать? Цитировать но ведь возможно что tmp > 0, а key уже -1, тогда после key-- он станет -2 (с печальными последствиями) Не вижу печальных последствий)) Ну пусть key == -2, ну и что? Следующий виток цикла: tmp = ++key; tmp == -1; key == -1; условие if (tmp) - не выполняется и как следствие нет и key--; переходим к следующему витку. Или я всё же не прав? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Апрель 01, 2011, 12:56 Не возражаю) Ок :)Мне интересно следующее: Пусть писатель W1 пишет данные, приходят ещё несколько писателей. Пока они ждут своей очереди. Теперь приходит читатель R1, читать пока запрещено, пока W1 пишет. Вопрос, когда будет разрешено читать R1? Когда допишет текущий W1 или пока допишут все писатели, что заявили о себе до прихода R1? В примерах реализаций этой темы - как получится, заявления писателей хранятся как булевский флажок. Реализация когда отслеживается и число заявок на запись возможна, но особой необходимости в ней нет, т.к. ждущий писатель немедленно выставит запрос опять если он был сброшен. Аналогичный вопрос, но с ситуацией, когда изначально читали несколько читателей. Приходит писатель. Пока ему писать запрещено: он ждёт. В это время приходят ещё читатели. Какого условие, разрешающее ему начать писать? Когда закончат чтение все начавшие ДО установки запроса на запись - в этом смысл pendingНе вижу печальных последствий)) Ну пусть key == -2, ну и что? Следующий виток цикла: Ну как же не выполняется: if (-1) возвращает true :)tmp = ++key; tmp == -1; key == -1; условие if (tmp) - не выполняется и как следствие нет и key--; переходим к следующему витку. Или я всё же не прав? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Апрель 01, 2011, 13:05 Цитировать Ну как же не выполняется: if (-1) возвращает true Улыбающийся Ой, совсем я уже того)) Разумеется нужно if (tmp > 0) --key; Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Апрель 01, 2011, 14:07 Цитировать Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность: Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0T1, T2 пришли, захватили ключи 0 и 1, key = 1 T1 отработал и ушёл, key = -1 T2 решил сделать --key, и завис навсегда (между key = -1 и -2) > 0 или != 0 неважно, ещё раз подробнее: T1, T2 пришли, захватили ключи 0 и 1, key = 1 T2 приготовился сделать --key T2 заснул T1 отработал и ушёл, key = -1 T2 проснулся, сделал --key, и завис навсегда между key = -1 и -2 Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Апрель 01, 2011, 14:33 Цитировать Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность: Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0T1, T2 пришли, захватили ключи 0 и 1, key = 1 T1 отработал и ушёл, key = -1 T2 решил сделать --key, и завис навсегда (между key = -1 и -2) > 0 или != 0 неважно, ещё раз подробнее: T1, T2 пришли, захватили ключи 0 и 1, key = 1 T2 приготовился сделать --key T2 заснул T1 отработал и ушёл, key = -1 T2 проснулся, сделал --key, и завис навсегда между key = -1 и -2 Вот как я это вижу (поэтапно): Код 1) Приходят писатели W1 и W2. 2) W1 делает key = ++m_key; (m_key = 0, key = 0) 3) W2 делает key = ++m_key; (m_key = 1, key = 1) 4) W1 проходит и начинает писать данные, W2 ждёт. 5) Пусть m_key == 1 и W2 приготовился сделать --key и заснул (соответственно key == 1). 6) W1 отработал и сделал m_key = -1; 7) W2 проснулся и сделал --m_key. Проверил не выполняется ли while (key != 0) - выполняется! 8) Делает следующий виток: key = ++m_key; (key == -1, m_key == -1) 9) W2 проверяет условие if (key > 0) - не выполняется! Не делает key-- ! 10) W2 проверяет условие while (key != 0) - выполняется! Делает следующий виток: 11) key = ++m_key; (key == 0, m_key == 0). 12) Проверяет условие if (key > 0) - не выполняется! Не делает --key!! 13) Проверяет условие while (key != 0) - Не выполняется! Начинает писать данные. Где ошибка?? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: brankovic от Апрель 01, 2011, 14:54 Прошу прощения, но я Вам не верю) да, действительно > 0 прошлый сценарий исключает. Тогда так: T1 захватил T2 пришёл и собрался делать --m_key, m_key = 1 T1 отпустил, m_key = -1, ушёл T3 пришёл, захватил (m_key был -1), m_key = 0 T2 сделал --m_key и на следующем витке захватил теперь T2 и T3 оба захватили мьютекс Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: Igors от Апрель 01, 2011, 15:22 1) Приходят писатели W1 и W2. Позвольте спросить: ну а на <..> создавать конструкцию которая может потребовать столь "углубленного анализа"? :) Это что, практично? Профессионально? Попробуйте один раз применить CAS - и, уверяю Вас, все проблемы исчезнут :)2) W1 делает key = ++m_key; (m_key = 0, key = 0) 3) W2 делает key = ++m_key; (m_key = 1, key = 1) 4) W1 проходит и начинает писать данные, W2 ждёт. 5) Пусть m_key == 1 и W2 приготовился сделать --key и заснул (соответственно key == 1). 6) W1 отработал и сделал m_key = -1; 7) W2 проснулся и сделал --m_key. Проверил не выполняется ли while (key != 0) - выполняется! 8) Делает следующий виток: key = ++m_key; (key == -1, m_key == -1) 9) W2 проверяет условие if (key > 0) - не выполняется! Не делает key-- ! 10) W2 проверяет условие while (key != 0) - выполняется! Делает следующий виток: 11) key = ++m_key; (key == 0, m_key == 0). 12) Проверяет условие if (key > 0) - не выполняется! Не делает --key!! 13) Проверяет условие while (key != 0) - Не выполняется! Начинает писать данные. Где ошибка?? Название: Re: AtomicData или механизм атомарной операции записи данных Отправлено: m_ax от Апрель 01, 2011, 16:31 Цитировать да, действительно > 0 прошлый сценарий исключает. Тогда так: Да, согласен)T1 захватил T2 пришёл и собрался делать --m_key, m_key = 1 T1 отпустил, m_key = -1, ушёл T3 пришёл, захватил (m_key был -1), m_key = 0 T2 сделал --m_key и на следующем витке захватил теперь T2 и T3 оба захватили мьютекс Цитировать Позвольте спросить: ну а на <..> создавать конструкцию которая может потребовать столь "углубленного анализа"? Улыбающийся Это что, практично? Профессионально? Попробуйте один раз применить CAS - и, уверяю Вас, все проблемы исчезнут Улыбающийся И опять-таки согласен)Короче, убедили) |