Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: m_ax от Март 24, 2011, 03:48



Название: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 24, 2011, 03:48
Вечер добрый)

Хочу спросить у бывалых в многопоточном программировании совета)
Ситуация следующая:
Имеется один поток, который пишет данные (в некотурую структуру) и есть несколько потоков, которые эти данные читают.
Сами данные представляются в виде класса и ссылка на объект этого класса передаётся пишущему и читающим потокам.
Ясно, что операция записи в общем случае не атомарна и могут возникнуть неприятности если один или более читающих потоков будут считывать даные, одновременно с записью их пишущим потоком.
Использовать mutex тоже не очень рационально, поскольку доступ к данным для чтения будет доступен только одному потоку.

Написать класс, который должен эту проблему решить, но есть сомнения...
Вот сам класс:
Код
C++ (Qt)
#ifndef ATOMICDATA_H
#define ATOMICDATA_H
 
template <class T>
class AtomicData
{
public:
   AtomicData() : m_flag(0) {}
   AtomicData(const T &data) : m_flag(0), m_data(data) {}
   AtomicData(const AtomicData<T> &other) : m_flag(0), m_data(other.get()) {}
   void set(const T &data) {
       while (m_flag) {}
       m_flag = 1;
       m_data = data;
       m_flag = 0;
   }
   T get() const {
       while (m_flag & 1) {}
       m_flag = 2;
       T data = m_data;
       m_flag = 0;
       return data;
   }
private:
   mutable volatile char m_flag;
   T m_data;
   AtomicData<T> &operator=(const AtomicData<T> &);
};
 
#endif // ATOMICDATA_H
 
 
Суть в том, что операция с 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
Код
C++ (Qt)
       while (m_flag) {}
                     <- Здесь пробой
       m_flag = 1;
 
:) На эти грабли будут наступать все и всегда, так что не расстраивайтесь  :)
Дело в том что когда управление вернулось из 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
Цитировать
Варианты

1) Сделать все самому. В нативных вызовах ничего особо страшного нет (OSSpinLockLock и InterlockedExchange для моих платформ, неплохо и QAtomicInt), но пройдя примерно половину пути приходишь к выводу - нерационально. Вот напр. у Вас пустое тело while. Эта, на первый взгляд несущественная мелочь, начинает доставлять неприятности - одно из ядер "загружено до упора" холостым ходом. Попытки решить это "сходу" (yield и.т.п) вызывают новые проблемы.

2) Использовать Qt QReadWriteLock. Здесь все прекрасно в том смысле что все решается очень быстро после чтения Assistant. Только одно маленькое "но" - Qt честно усыпляет нитки, поэтому при достаточно интенсивных блокировках скорость будет убита в ноль (N ядер медленнее одного).

3) После долгого обдумывания я выбрал Intel TBB и весьма доволен - шикарный выбор локов, atomic и scoped_lock.
А что Вы можете сказать о библиотеки ZThread?
Судя по отзывам она неплоха)


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 24, 2011, 21:47
А что Вы можете сказать о библиотеки ZThread?
Судя по отзывам она неплоха)
Могу сказать немного - бегло рассматривал как возможный вариант. Не понравился из-за (насколько я помню, могу напутать)

- выглядит старой (неск лет не обновлялась)
- масса template, вникнуть трудно
- с локами бедненько (как и с ожиданием)


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 25, 2011, 03:47
У меня ещё такой вопрос возник:
Является ли следующая операция атомарна? (m_flag - это char):
Код
C++ (Qt)
while (m_flag++);  // <<-- Атомарна ли оно?
critical_section();
m_flag = 0;
 


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: brankovic от Март 25, 2011, 09:20
У меня ещё такой вопрос возник:
Является ли следующая операция атомарна? (m_flag - это char):
Код
C++ (Qt)
while (m_flag++);  // <<-- &#1040;&#1090;&#1086;&#1084;&#1072;&#1088;&#1085;&#1072; &#1083;&#1080; &#1086;&#1085;&#1086;?
critical_section();
m_flag = 0;
 

Из обычных операций атомарны только запись и чтение, и только того, что не больше 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
Код
C++ (Qt)
while (m_flag++);  // <<-- Атомарна ли оно?
critical_section();
m_flag = 0;
 
Нет, и замена ++ на атомарный инкремент здесь ничего не даст (ввиду тех же граблей).


Название: 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 {}

class DataManager
{
public:
    ...
    SharedPointer<Data> data() const
    {
         ReadLocker lock;
         return m_data;
    }

    void setData(SharedPointer<Data> d)
    {
         WriteLocker lock;
         m_data = d;
    }
private:
    SharedPointer<Data> m_data;
}

Как я понял lock-free storage делает то же самое - атомарный обмен указателей, только меньше оверхед + решение ряда других проблем.
Вот я и думаю стоит ли заморачиваться с технологией lock-free ради оверхэда использования мьютексов.
Но да, идея lock-free достаточно интересная.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Март 26, 2011, 10:52
Код:
class Data {}

class DataManager
{
public:
    ...
    SharedPointer<Data> data() const
    {
         ReadLocker lock;
         return m_data;
    }

    void setData(SharedPointer<Data> d)
    {
         WriteLocker lock;
         m_data = d;
    }
private:
    SharedPointer<Data> m_data;
}

Именно так все и делают, за счёт shared_ptr оверхеда здесь мизер. Собственно 2 атомарных операции на захват/отдачу мьютекса + 1 атомарная операция на копирование shared_ptr. В лучшем lockfree решении будет 2 атомарных операции. Только для этого lockfree не оправдано.

lockfree незаменимо, если очень хочется сделать код reentarable, например для доступа из юникс-сигналов. Но  задачу, где это необходимо, ещё придумать надо.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 26, 2011, 12:11
Код:
class Data {}
...
    SharedPointer<Data> data() const
    {
         ReadLocker lock;
         return m_data;
    }

    void setData(SharedPointer<Data> d)
    {
         WriteLocker lock;
         m_data = d;
    }
Не понял Вашей задумки: m_data "улетела" (из метода data) и тот кто ее подхватит будет использовать указатель на ее данные. Этот "улетевший" указатель не будет обновлен после setData, в чем тогда смысл?


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 26, 2011, 14:20
Цитировать
Именно так все и делают, за счёт shared_ptr оверхеда здесь мизер. Собственно 2 атомарных операции на захват/отдачу мьютекса + 1 атомарная операция на копирование shared_ptr. В лучшем lockfree решении будет 2 атомарных операции. Только для этого lockfree не оправдано.
А за счёт чего достигается атомарность копирования shared_ptr? Вот например:
Код
C++ (Qt)
shared_ptr<Data> ptr;
DataManager dataManager;
...
ptr = dataManager.data();
 
При копировании, на сколько я понял должно произойти как минимум следующее:
ptr - должен уменьшить свой счётчик ссылок и если он окажется 0 удалить сам объект, скопировать указатель на новые данные и опять увеличить счётчик ссылок.
Наверное я чегото не допонимаю в этом, почему оно атомарно? 
увеличение числа ссылок, копирование указателя на объект


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Март 26, 2011, 16:25
А за счёт чего достигается атомарность копирования shared_ptr? Вот например:
Код
C++ (Qt)
shared_ptr<Data> ptr;
DataManager dataManager;
...
ptr = dataManager.data();
 
При копировании, на сколько я понял должно произойти как минимум следующее:
ptr - должен уменьшить свой счётчик ссылок и если он окажется 0 удалить сам объект,
скопировать указатель на новые данные и опять увеличить счётчик ссылок.
Наверное я чегото не допонимаю в этом, почему оно атомарно? 

Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 26, 2011, 16:46
Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос.
:) Попробую я пояснить

Код
C++ (Qt)
shared_ptr<Data> ptr;
DataManager dataManager;
...
for (int i = 0; i < 100 ++i) {
ptr = dataManager.data();
...
}
 
Вполне возможно что напр после первых 10 итераций цикла др. нитка поставит др. data. В этом случае следующие итерации цикла будут работать уже с новыми данными. Это "атомарно" и будет работать корректно. Однако это совсем не значит что данные будут изменены для того кто их уже получил (до замены) с помощью data() - что получил, с тем и работает


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 26, 2011, 19:36
Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос.
:) Попробую я пояснить

Код
C++ (Qt)
shared_ptr<Data> ptr;
DataManager dataManager;
...
for (int i = 0; i < 100 ++i) {
ptr = dataManager.data();
...
}
 
Вполне возможно что напр после первых 10 итераций цикла др. нитка поставит др. data. В этом случае следующие итерации цикла будут работать уже с новыми данными. Это "атомарно" и будет работать корректно. Однако это совсем не значит что данные будут изменены для того кто их уже получил (до замены) с помощью data() - что получил, с тем и работает

Да, это я понял) Всмысле, это вполне нормальное поведение (кто успел - тот съел :) )

А за счёт чего достигается атомарность копирования shared_ptr? Вот например:
Код
C++ (Qt)
shared_ptr<Data> ptr;
DataManager dataManager;
...
ptr = dataManager.data();
 
При копировании, на сколько я понял должно произойти как минимум следующее:
ptr - должен уменьшить свой счётчик ссылок и если он окажется 0 удалить сам объект,
скопировать указатель на новые данные и опять увеличить счётчик ссылок.
Наверное я чегото не допонимаю в этом, почему оно атомарно? 

Только за счёт мьютекса, в данном примере. Дальше боюсь запутать, пытаясь домыслить неправильно поставленный вопрос.

Хорошо, тогда получается, что сама по себе операция копиррования и присваивания двух shared_ptr (я иммею в виду без участия DataManager) не является атомарной?
А, если является, то это обеспечивается собственным мьютексом в самом 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 {}

class DataManager
{
public:
    ...

    // Раньше было data()
    SharedPointer<Data> getDataSnapshot() const
    {
         ReadLocker lock;
         return m_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 {}

class DataManager
{
public:
    ...

    // Раньше было data()
    SharedPointer<Data> getDataSnapshot() const
    {
         ReadLocker lock;
         return m_data;
    }
    ...
}

Здесь мне видится один недостаток: после получения SharedPointer<Data> у нас будет возможность менять данные по указателю. Но с практической точки зрения работать будет быстрее, чем делать класс Data синхронизированным. Да и обойти это можно в принципе.



Для большей строгости можно использовать const propagation:
Код:

...  
     const SharedPointer<Data> getDataSnapshot()
    {
         ReadLocker lock;
         return m_data;
    }

    const SharedPointer<const Data> getDataSnapshot() const
    {
         return const_cast<DataManager*>(this)->getDataSnapshort();
    }
...

Соответственно, у кого const ссылка на DataManager не может изменить состояние данных.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Март 27, 2011, 18:59
Для большей строгости можно использовать const propagation:
...
Соответственно, у кого const ссылка на DataManager не может изменить состояние данных.

а почему не сделать так:

Код
C++ (Qt)
   const SharedPointer<const Data> getDataSnapshot () const
   {
        ReadLocker lock;
        return m_data;
   }
 

в чём смысл второго метода с не-конст указателем, если полученный объект нельзя менять всё равно?


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: vregess от Март 27, 2011, 19:10

Для большей строгости можно использовать const propagation:

Да, я перемудрил. Так лучше.
А еще можно мьютекс сделать mutable и оставить только константный getDataSnapshot()

Код:

...  
     SharedPointer<const Data> getDataSnapshot() const
    {
         ReadLocker lock(m_mutex);
         return m_data;
    }

private:
    mutable Mutex m_mutex;


brankovic уже подметил


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Akon от Март 27, 2011, 19:51
Для большей строгости можно использовать const propagation:
...
Соответственно, у кого const ссылка на DataManager не может изменить состояние данных.

а почему не сделать так:

Код
C++ (Qt)
   const SharedPointer<const Data> getDataSnapshot () const
   {
        ReadLocker lock;
        return m_data;
   }
 

в чём смысл второго метода с не-конст указателем, если полученный объект нельзя менять всё равно?

почему полученный объект (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).
Код
C++ (Qt)
#ifndef ATOMICDATA_H
#define ATOMICDATA_H
 
#include <asm/atomic.h>
 
template <class T>
class AtomicData
{
private:
   AtomicData() {
       m_key1 = ATOMIC_INIT(-1);
       m_key2 = ATOMIC_INIT(-1);
   }
   void setData(const T &data) {
       while (!test_for_write(&m_key1, &m_key2));
       // critical section
       m_data = data;
 
       atomic_set(&m_key1, -1);
       atomic_set(&m_key2, -1);
   }
   T getData() const {
       while (!test_for_read(&m_key1));
       // critical section
       T res = m_data;
       atomic_set(&m_key1, -1);
       atomic_set(&m_key2, -1);
       return res;
   }
private:
   atomic_t m_key1;
   atomic_t m_key2;
   T m_data;
 
   static bool test_for_write(atomic_t *key1, atomic_t *key2) {
       bool res1 = atomic_inc_and_test(key1);
       bool res2 = atomic_inc_and_test(key2);
       atomic_set(key1, 0);
       atomic_set(key2, 0);
       return (res1 && res2);
   }
 
   static bool test_for_read(atomic_t *key1) {
       bool res = atomic_inc_and_test(key1);
       atomic_set(&m_key2, 0);
       atomic_set(key1, -1);
       return res;
   }
};
 
 
#endif // ATOMICDATA_H
 

Где 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>
Код
C++ (Qt)
#ifndef ATOMICDATA_H
#define ATOMICDATA_H
 
#include <cstdatomic>
 
template <class T>
class AtomicData
{
private:
   typedef atomic<int> AtomicInt;
   AtomicData()  : m_key1(-1), m_key2(-1) {}
   void setData(const T &data) {
       while (!test_for_write(m_key1, m_key2));
       // critical section
       m_data = data;
 
       m_key1 = -1;
       m_key2 = -1;
   }
   T getData() const {
       while (!test_for_read(m_key1, m_key2));
       // critical section
       T res = m_data;
       m_key1 = -1;
       m_key2 = -1;
       return res;
   }
private:
   AtomicInt m_key1;
   AtomicInt m_key2;
   T m_data;
 
   static bool test_for_write(AtomicInt &key1, AtomicInt &key2) {
       bool res1 = (++key1 == 0);
       bool res2 = (++key2 == 0);
       key1 = 0;
       key2 = 0;
       return (res1 && res2);
   }
 
   static bool test_for_read(AtomicInt &key1, AtomicInt &key2) {
       bool res = (++key1 == 0);
       key2 = 0;
       key1 = -1;
       return res;
   }
};
 


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 28, 2011, 11:12
Код
C++ (Qt)
   static bool test_for_read(AtomicInt &key1, AtomicInt &key2) {
       bool res = (++key1 == 0);
       key2 = 0;
       key1 = -1;
       return res;
   }
};
 
Грабли те же. Да, ++key1 атомарно, так что с того? В момент когда выполняется key2 = 0, др. нитка могла уже сделать с key1 все что угодно и возвращаемый res ничему не соответствует.

Без "идиомы CAS" не обойтись. Просто "присваивание" (напр key1 = -1) почти всегда неверно (хоть и атомарно).

И если уж взялись писать свой ReadWriteLocker, то делайте и "write pending" (запрос на запись) чтобы желающие, увидев этот флаг, не захватывали по чтению - иначе запись может не пробиться.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 28, 2011, 12:49
Код
C++ (Qt)
   static bool test_for_read(AtomicInt &key1, AtomicInt &key2) {
       bool res = (++key1 == 0);
       key2 = 0;
       key1 = -1;
       return res;
   }
};
 
Грабли те же. Да, ++key1 атомарно, так что с того? В момент когда выполняется key2 = 0, др. нитка могла уже сделать с key1 все что угодно и возвращаемый res ничему не соответствует.

Без "идиомы CAS" не обойтись. Просто "присваивание" (напр key1 = -1) почти всегда неверно (хоть и атомарно).

И если уж взялись писать свой ReadWriteLocker, то делайте и "write pending" (запрос на запись) чтобы желающие, увидев этот флаг, не захватывали по чтению - иначе запись может не пробиться.
Смотрите: Другая нитка конечно можнет сделать с key1 всё что угодно, но результат то уже будет другой)
Например вначале key1 = -1. После первой проверки с инкрементом:
Код
C++ (Qt)
res1 = (++key == 0)
 

Мы получим res1 = true и key1 = 0; Далее другая нитка делает тоже, но key1 будет уже равен 1 и res вернёт false
 :)   


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Март 28, 2011, 13:57
...

не понял задумки, по этой функции получается, что любой, кто выходит из критической секции её разлочивает. Т.е. 2 треда вошли в секцию, потом один заснул, а другой вышел. Пришёл третий и залочит на запись, SIGSEGV. Я правильно понимаю, что ридеров допускается несколько?

Код
C++ (Qt)
   T getData() const {
       while (!test_for_read(m_key1, m_key2));
       // critical section
       T res = m_data;
       m_key1 = -1;
       m_key2 = -1;
       return res;
   }
 


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 28, 2011, 14:11
Код
C++ (Qt)
   static bool test_for_write(AtomicInt &key1, AtomicInt &key2) {
       bool res1 = (++key1 == 0);
       bool res2 = (++key2 == 0);
       key1 = 0;
       key2 = 0;
       return (res1 && res2);
   }
 
Пусть res1 и res2 оказались равны true. Запрос на запись получен и выполняется m_data = data, В этот момент др. нитка захотела читать

Код
C++ (Qt)
   static bool test_for_read(AtomicInt &key1, AtomicInt &key2) {
       bool res = (++key1 == 0);
       key2 = 0;
       key1 = -1;
       return res;
   }
};
 
Это крутится в 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 - число читающих

Код
C++ (Qt)
bool AcquireWrite( AtomicInt & state )
{
 if (state > 0) return false;
 return CompareAndSwap(&state, 0, STATE_WRITE);
}
 
void ReleaseWrite( AtomicInt & state )
{
 state = 0;
}
 
bool AcquireRead( AtomicInt & state )
{
 int temp = state;
 if (temp == STATE_WRITE) return false;
 return CompareAndSwap(&state, temp, temp + 1);
}
 
void ReleaseRead( AtomicInt & state )
{
 assert(state > 0);
 --state;
}
 
Здесь нет write_pending - ну чтобы было о чем подумать  :)


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Akon от Март 28, 2011, 15:16
Пришло в голову вот такое lockfree решение для вышеупомянутого DataManager. Это решение для одного писателя и нескольких читателей (если писателей несколько, то их пришлось бы синхронизировать).

Код:
struct Data 
{
int a;
int b;
...

Data& directAssignment(const Data& other)
{
a = other.a;
b = other.b;
...
return *this;
}

Data& reverseAssignment(const Data& other)
{
b = other.b;
a = other.a;
...
return *this;
}
};

class DataManager
{
public:
// called by reader threads (several threads)
const Data data() const
{
const Data result = m_data1;

while (result != m_data2)
result = m_data1;

return result;
}

// called by writer thread (single thread)
    void setData(const Data& value)
    {
if (value == m_data1) return;

m_data1.directAssignment(value);
m_data2.reverseAssignment(value);
    }

private:
volatile Data m_data1;
volatile Data m_data2;
}

В коде нет ни одной блокировки! Имеются два присваивания - в прямом и инверсном порядке, что гарантирует консистентность 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
Код
C++ (Qt)
const Data data() const
{
const Data result = m_data1; /*B*/
 
while (result != m_data2)
result = m_data1;
 
return result;
}
 
   void setData (const Data & value)
   {
if (value == m_data1) return;
 
m_data1.directAssignment(value); /*A*/
m_data2.reverseAssignment(value);
   }
 


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}.
Согласен. Тогда может лучше так

Код
C++ (Qt)
const Data data() const
{
const Data result = m_data1; /*B*/
 
while (result != m_data1 || result != m_data2)
result = m_data1;
 
return result;
}
 
Чтобы обойтись без утомительного сравнения "задом наперед"




Название: 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
И так, вроде ничего не упустил)
Код
C++ (Qt)
#ifndef ATOMICDATA_H
#define ATOMICDATA_H
 
#include <assert.h>
#include <cstdatomic>
 
template <class T>
class AtomicData
{
public:
   typedef atomic<int> AtomicInt;
   AtomicData()  : m_count_readers(0), m_state_write(-1) {}
 
   void setData(const T &data) {
       while (!acquireWrite(m_count_readers, m_state_write));
       m_data = data;
       releaseWrite(m_state_write);
   }
   T getData() const {
       while (!acquireRead(m_count_readers, m_state_write));
       T data = m_data;
       releaseRead(m_count_readers, m_state_write);
       return data;
   }
private:
   AtomicInt m_count_readers;
   AtomicInt m_state_write;
   T m_data;
 
   static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       state_write = 0;
 
       return (tmp && !readers);
   }
   //-------------------------------------------------------------------------
 
   static void releaseWrite(AtomicInt &state_write) {
       state_write = -1;
   }
   //-------------------------------------------------------------------------
 
   static bool acquireRead(AtomicInt &readers, AtomicInt &state_write) {
 
       if (!readers)
           readers = (state_write == -1);
       else
           readers++;
 
       return readers;
   }
   //-------------------------------------------------------------------------
 
   static void releaseRead(AtomicInt &readers, AtomicInt &state_write) {
       if (--readers == 0)
           state_write = -1;
   }
   //-------------------------------------------------------------------------
};
 
 
#endif // ATOMICDATA_H
 
Пояснения:
m_counter_readers - число читающих потоков (в данный момент времени)
m_state_write - если = -1 - никто не пишет, в противном случае есть ожидающие записи

Читать данные могут одновременно несколько потоков, записывать всегда только какой-то один.
Если кто-то пишет, читать запрещено.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 28, 2011, 18:59
R проснулся, прочитал BA, в стеке у него BA BA, он их сравнил и остался доволен, но data никогда не принимала значения BA
Верно, совпадение возможно и при обратном порядке присваивания

Код
C++ (Qt)
       if (!readers)
           readers = (state_write == -1);
 
Те же грабли. Последняя строка не атомарна.
Цитировать
С Ларисой я действительно пил вино, но это было на пасху
Т.е. state_write действительно был -1, но до тех пор пока это присвоится readers, др. нитка успеет захватить по записи.

Лучше держать все в одном AtomicInt. с двумя переменными заморочек намного больше. Ваше упорство избежать CAS "заслуживает лучшего применения"  :) И как там у Вас с холостым ходом?


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 28, 2011, 19:12
Код
C++ (Qt)
       if (!readers)
           readers = (state_write == -1);
 
Те же грабли. Последняя строка не атомарна.
Цитировать
С Ларисой я действительно пил вино, но это было на пасху
Т.е. state_write действительно был -1, но до тех пор пока это присвоится readers, др. нитка успеет захватить по записи.

Лучше держать все в одном AtomicInt. с двумя переменными заморочек намного больше. Ваше упорство избежать CAS "заслуживает лучшего применения"  :) И как там у Вас с холостым ходом?
[/quote]
Не атомарно? Ну это можно исправить следующим образом:
Код
C++ (Qt)
static bool acquireRead(AtomicInt &readers, AtomicInt &state_write) {
 
       if (!readers)
           readers = 1; // атомарно
           int tmp = (state_write == -1); // атомарно
           readers = tmp; // атомарно
       else
           readers++;
 
       return readers;
   }
 
Цитировать
И как там у Вас с холостым ходом?
В смысле? С ним что-то не так?


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 28, 2011, 19:22
Не атомарно? Ну это можно исправить следующим образом:
Код
C++ (Qt)
       if (!readers)
           readers = 1; // атомарно
 
Опять не атомарно - ведь N ниток могли увеличить readers "между 2-мя строками" и, присваивая единицу, Вы это значение теряете

Цитировать
И как там у Вас с холостым ходом?
В смысле? С ним что-то не так?
В том смысле что его вообще пока не видно - тело while отсутствует. Это означает что ядро будет молотить впустую и, как показала жизнь, это не так уж безобидно.



Название: 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
А что Вы скажите на это  ;):
Код
C++ (Qt)
static bool acquireRead(AtomicInt &readers, AtomicInt &state_write) {
 
       if (!readers) {
           int tmp = (++state_write == 0);
           readers = tmp;
       }
       else
           readers++;
 
       return readers;
   }
 


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: m_ax от Март 28, 2011, 20:09
это как-то уж слишком коварно.. По смыслу m_ax спинлок хотел, а в спинлоке так и должно быть.
Не должно. Про свой OSX могу сказать: OSSpinLockLock (спиннер) не просто "крутит while". А TBB сначала просто делает какое-то число nop, затем вставляет pause а потом уж shed_yield. При этом учитывается специфика процессора. В общем, это на порядок сложнее чем то что сейчас обсуждается  :)  

А без этого (увы) могут быть заметные тормоза для др. задач
Если удасца реализовать первоночальный SpinLock, то его можно будет ускорить создав буфер даных. Тогда простои при записи и чтении можно будет существенно сократить. 


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 28, 2011, 20:23
Короче, окончательный вариант SpinLock:
Код
C++ (Qt)
#ifndef SPINLOCK_H
#define SPINLOCK_H
 
#include <assert.h>
#include <cstdatomic>
 
template <class T>
class SpinLock
{
public:
   typedef atomic<int> AtomicInt;
   SpinLock()  : m_count_readers(0), m_state_write(-1) {}
 
   void setData(const T &data) {
       while (!acquireWrite(m_count_readers, m_state_write));
       m_data = data;
       releaseWrite(m_state_write);
   }
   T getData() const {
       while (!acquireRead(m_count_readers, m_state_write));
       T data = m_data;
       releaseRead(m_count_readers, m_state_write);
       return data;
   }
private:
   AtomicInt m_count_readers;
   AtomicInt m_state_write;
   T m_data;
 
   static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       state_write = 0;
 
       return (tmp && !readers);
   }
   //-------------------------------------------------------------------------
 
   static void releaseWrite(AtomicInt &state_write) {
       state_write = -1;
   }
   //-------------------------------------------------------------------------
 
   static bool acquireRead(AtomicInt &readers, AtomicInt &state_write) {
 
       if (!readers) {
           int tmp = (++state_write == 0);
           readers = tmp;
       }
       else
           readers++;
 
       return readers;
   }
   //-------------------------------------------------------------------------
 
   static void releaseRead(AtomicInt &readers, AtomicInt &state_write) {
       if (--readers == 0)
           state_write = -1;
   }
   //-------------------------------------------------------------------------
};
 
#endif // SPINLOCK_H
 


Название: 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:
Код
C++ (Qt)
static bool acquireRead(AtomicInt &readers, AtomicInt &state_write)
{
  if (!readers)
  {
     int tmp = (++state_write == 0);
     readers = tmp;
  }
  else
     readers++;
 
  return readers;
}
 

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 
{
int a;
int b;
...

Data& directAssignment(const Data& other)
{
a = other.a;
b = other.b;
...
return *this;
}

Data& reverseAssignment(const Data& other)
{
b = other.b;
a = other.a;
...
return *this;
}
};

class DataManager
{
public:
// called by reader threads (several threads)
const Data data() const
{
const Data result = m_data1;
int counter = counter_;

while (result != m_data2 || counter != counter_) {
result = m_data1;
counter = counter_;
}

return result;
}

// called by writer thread (single thread)
    void setData(const Data& value)
    {
if (value == m_data1) return;

++counter_;
m_data1.directAssignment(value);
m_data2.reverseAssignment(value);
    }

private:
volatile Data m_data1;
volatile Data m_data2;
volatile int counter_;
}


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Akon от Март 28, 2011, 23:10
Да нет, тоже не пойдет


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 29, 2011, 10:20
Хм.. Ещё один вариант, надеюсь последний)
Код
C++ (Qt)
#ifndef SPINLOCK_H
#define SPINLOCK_H
 
#include <assert.h>
#include <cstdatomic>
 
template <class T>
class SpinLock
{
public:
   typedef atomic<int> AtomicInt;
   SpinLock()  : m_count_readers(0), m_state_write(-1) {}
 
   void setData(const T &data) {
       while (!acquireWrite(m_count_readers, m_state_write));
       m_data = data;
       releaseWrite(m_state_write);
   }
   T getData() const {
       while (!acquireRead(m_count_readers, m_state_write));
       T data = m_data;
       releaseRead(m_count_readers, m_state_write);
       return data;
   }
private:
   AtomicInt m_count_readers;
   AtomicInt m_state_write;
   T m_data;
 
   static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       state_write = 0;
 
       return (tmp && !readers);
   }
   //-------------------------------------------------------------------------
 
   static void releaseWrite(AtomicInt &state_write) {
       state_write = -1;
   }
   //-------------------------------------------------------------------------
 
   static bool acquireRead(AtomicInt &readers, AtomicInt &state_write) {
 
       readers++;
       bool tmp = (++state_write == 0);
       state_write = 0;
 
       if (tmp) {
           state_write = -1;
           return true;
       }
       readers--;
       return false;
   }
   //-------------------------------------------------------------------------
 
   static void releaseRead(AtomicInt &readers, AtomicInt &state_write) {
       if(--readers == 0)
           state_write = -1;
   }
   //-------------------------------------------------------------------------
};
 
 
#endif // SPINLOCK_H
 
Во всяком случае, ситуация описанная brankovic:
Цитировать
R0 читал
R1 дошёл до if, увидел, что кто-то читает, отправился на else
R1 заснул
R0 ушёл (readers == 0)
W пришёл и захватил на запись
R1 проснулся, сделал ++readers и вернул true

теперь R0 читает, а W пишет
исключена.


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Март 29, 2011, 10:45
Хм.. Ещё один вариант, надеюсь последний)
Код
C++ (Qt)
   static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       state_write = 0;
 
       return (tmp && !readers);
   }
 
   static void releaseRead(AtomicInt &readers, AtomicInt &state_write) {
       if(--readers == 0)
           state_write = -1;
   }
};
 

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 и завис навсегда
а мы вот так:
Код
C++ (Qt)
static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       if (tmp && !readers)
           return true;
 
       state_write = 0;
       return false;
   }
 
:)


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: m_ax от Март 29, 2011, 11:10
Если удасца реализовать первоночальный SpinLock, то его можно будет ускорить создав буфер даных. Тогда простои при записи и чтении можно будет существенно сократить. 
Не понял, расскажите подробнее
В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев.
Мне проще код накидать)) Но это когда разберусь со SpinLock ом. 


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: brankovic от Март 29, 2011, 11:49
а мы вот так:
Код
C++ (Qt)
static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       if (tmp && !readers)
           return true;
 
       state_write = 0;
       return false;
   }
 
:)

никого не было
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
а мы вот так:
Код
C++ (Qt)
static bool acquireWrite(AtomicInt &readers, AtomicInt &state_write) {
 
       bool tmp = (++state_write == 0);
       if (tmp && !readers)
           return true;
 
       state_write = 0;
       return false;
   }
 
Так то же самое: перед выполнением state_write = 0; все readers могли отстреляться, имеем deadlock, т.к. никто не может снять state_write с нуля. И как бы Вы ни крутили, без CAS дырка всегда найдется  :)

В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев.
Мне проще код накидать)) Но это когда разберусь со SpinLock ом.  
Это интересно обсудить, но выделите пожалуйста в отдельную тему (предполагаем что какой-то ReadWriteLock имеется)



Название: 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.
Несколько пересмотрел логику и теперь это выглядит так:
Код
C++ (Qt)
#ifndef SPINLOCK_H
#define SPINLOCK_H
 
#include <assert.h>
#include <cstdatomic>
 
template <class T>
class SpinLock
{
public:
   typedef atomic<int> AtomicInt;
   SpinLock()  : m_key(-1), m_state(STATE_FREE) {}
 
   void setData(const T &data) {
       while (!acquireWrite(m_key, m_state));
       m_data = data;
       releaseWrite(m_key, m_state);
   }
   T getData() const {
       while (!acquireRead(m_key, m_state));
       T data = m_data;
       releaseRead(m_key, m_state);
       return data;
   }
private:
   enum { STATE_FREE = 0, STATE_READ = 1, STATE_WRITE = 2, STATE_READ_AND_WRITE = 3 };
   AtomicInt m_key;
   AtomicInt m_state;
   T m_data;
   //-------------------------------------------------------------------------
 
   static bool acquireWrite(AtomicInt &key, AtomicInt &state) {
 
       int tmp = (state |= STATE_WRITE);
       if (tmp == STATE_READ_AND_WRITE) {
           state = STATE_READ;
           return false;
       }
       bool tmp2 = (++key == 0);
       key = 0;
       return tmp2;
   }
   //-------------------------------------------------------------------------
 
   static void releaseWrite(AtomicInt &key, AtomicInt &state) {
       state = STATE_FREE;
       key = -1;
   }
   //-------------------------------------------------------------------------
 
   static bool acquireRead(AtomicInt &/*key*/, AtomicInt &state) {
 
       int tmp = (state |= STATE_READ);
       if (tmp == STATE_READ)
           return true;
       state = STATE_WRITE;
       return false;
 
   }
   //-------------------------------------------------------------------------
 
   static void releaseRead(AtomicInt &/*key*/, AtomicInt &state) {
      state = STATE_FREE;
   }
   //-------------------------------------------------------------------------
};
 
 
#endif // SPINLOCK_H
 


Краткое описание:
Есть две переменные 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 только когда все читатели перестанут читать:
Код
C++ (Qt)
#ifndef SPINLOCK_H
#define SPINLOCK_H
 
#include <assert.h>
#include <cstdatomic>
 
template <class T>
class SpinLock
{
public:
   typedef atomic<int> AtomicInt;
   SpinLock()  : m_key(-1), m_state(STATE_FREE), m_counter(0) {}
 
   void setData(const T &data) {
       while (!acquireWrite(m_key, m_state));
       m_data = data;
       releaseWrite(m_key, m_state);
   }
   T getData() const {
       while (!acquireRead(m_counter, m_state));
       T data = m_data;
       releaseRead(m_counter, m_state);
       return data;
   }
private:
   enum { STATE_FREE = 0, STATE_READ = 1, STATE_WRITE = 2, STATE_READ_AND_WRITE = 3 };
   AtomicInt m_key;
   AtomicInt m_state;
   AtomicInt m_counter;
   T m_data;
   //-------------------------------------------------------------------------
 
   static bool acquireWrite(AtomicInt &key, AtomicInt &state) {
 
       int tmp = (state |= STATE_WRITE);
       if (tmp == STATE_READ_AND_WRITE) {
           state = STATE_READ;
           return false;
       }
       bool tmp2 = (++key == 0);
       key = 0;
       return tmp2;
   }
   //-------------------------------------------------------------------------
 
   static void releaseWrite(AtomicInt &key, AtomicInt &state) {
       state = STATE_FREE;
       key = -1;
   }
   //-------------------------------------------------------------------------
 
   static bool acquireRead(AtomicInt &counter, AtomicInt &state) {
 
       int tmp = (state |= STATE_READ);
       if (tmp == STATE_READ) {
           counter++;
           return true;
       }
       state = STATE_WRITE;
       return false;
 
   }
   //-------------------------------------------------------------------------
 
   static void releaseRead(AtomicInt &counter, AtomicInt &state) {
       if (--counter == 0)
           state = STATE_FREE;
   }
   //-------------------------------------------------------------------------
};
 
 
#endif // SPINLOCK_H
 
Вот так)


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 29, 2011, 20:32
m_ax, просьба: давайте по пустому не месить и форум понапрасну не утомлять.

Догнать а что же такое STATE_READ_AND_WRITE - мудрено. Что-то типа "Rat but blue"?  :)

Код
C++ (Qt)
   static bool acquireRead(AtomicInt &counter, AtomicInt &state) {
 
       int tmp = (state |= STATE_READ);
       if (tmp == STATE_READ) {
 
Как уже не раз говорилось, между этими 2-мя строчками может произойти многое, "просто if" здесь не катит (поезд уже ушел)

Я уважаю желание "докопаться самому" и с подозрением отношусь к "начитавшимся классов", но чего ж об один камень 10 раз спотыкаться?  :)  Зачем упорно избегать CAS который предназначен для решения таких коллизий? Зачем рыть себе яму привлекая вторую переменную за которой надо (мучительно) следить?


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: brankovic от Март 30, 2011, 00:06
Код
C++ (Qt)
   static bool acquireWrite(AtomicInt &key, AtomicInt &state) {
 
       int tmp = (state |= STATE_WRITE);
       if (tmp == STATE_READ_AND_WRITE) {
           state = STATE_READ;
           return false;
       }
 

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>
class SpinLock
{
public:
const T getData() const
{
m_state.fetchAndInc();  // add reader
while (m_state & WriterMask);  // writer presence
const T result = m_data;
m_state.fetchAndDec();  // remove reader
return result;
}

void setData(const T &data)
{
while (m_state.fetchAndOr(WriterMask) & ReadersMask);  // readers presence
m_state.fetchAndXor(WriterMask);  // remove writer presence to prevent deadlock
m_data = data;
m_state.fetchAndXor(WriterMask);  // remove writer presence
}

private:
static const int WriterMask = 0x80000000;
static const int ReaderMask = 0xFFFFFFFF ^ WriterMask;

AtomicInt m_state;
T m_data;
};

Ограничения: только один писатель, проблема переполнения счетчика опущена.

Смысл опрераций:
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
Нужно увеличивать скважность импульса, например, отказаться от остатка кванта

Код:
...
while (m_state.fetchAndOr(WriterMask) & ReadersMask) {  // readers presence
m_state.fetchAndXor(WriterMask);  // remove writer presence to prevent deadlock
sleep(0);
}
...


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 30, 2011, 10:14
Смысл опрераций:
AtomicInt::fetchAndInc
AtomicInt::fetchAndDec
AtomicInt::fetchAndOr
AtomicInt::fetchAndXor
думаю всем понятен.
Без них можно обойтись

Код
C++ (Qt)
#define FLAG_WRITE      1   //  флаг запись
#define FLAG_PENDING   2   //  запрос запись
#define FLAG_WRITE_PENDING   (FLAG_WRITE | FLAG_PENDING)    
#define NUM_READERS(a)  ((a) >> 2)
 
bool AcquireWrite( AtomicInt & state )
{
 int curValue = state;
 if (curValue & FLAG_WRITE)   // кто-то уже пишет?
   return false;
 
 if (!NUM_READERS(curValue))                // нет читающих, пытаемся захватить по записи
  return CompareAndSwap(&state, curValue, FLAG_WRITE_PENDING);
 
 if ((curValue & FLAG_PENDING) == 0)   // пытаемся долить запрос на запись
  CompareAndSwap(&state, curValue, curValue | FLAG_PENDING);
 
 return false;
}
 
bool AcquireRead( AtomicInt & state )
{
 int curValue = state;
 if (curValue & FLAG_WRITE_PENDING)   // кто-то пишет или хочет писать
   return false;
 
 return CompareAndSwap(&state, curValue, (NUM_READERS(curValue) + 1) << 2);
}
 
void Release( AtomicInt & state )
{
 int curValue = state;
 if (curValue & FLAG_WRITE) {  // сброс записи
  assert(NUM_READERS(curValue) == 0);
  state = 0;        
 }
 
 else {     // уменьшаем число читающих, но не должны потерять FLAG_PENDING, поэтому while
   assert(NUM_READERS(curValue) > 0);
   while (true) {
     curValue = state;
     int newValue = (curValue & FLAG_WRITE_PENDING) | ((NUM_READERS(curValue) - 1) << 2);
     if (CompareAndSwap(&state, curValue, newValue))
      return;
   }
 }
}
 
Ну CompareAndSwap надо найти в хедере для своей платформы


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 30, 2011, 13:11
m_ax, просьба: давайте по пустому не месить и форум понапрасну не утомлять.

Догнать а что же такое STATE_READ_AND_WRITE - мудрено. Что-то типа "Rat but blue"?  :)

Код
C++ (Qt)
   static bool acquireRead(AtomicInt &counter, AtomicInt &state) {
 
       int tmp = (state |= STATE_READ);
       if (tmp == STATE_READ) {
 
Как уже не раз говорилось, между этими 2-мя строчками может произойти многое, "просто if" здесь не катит (поезд уже ушел)

Я уважаю желание "докопаться самому" и с подозрением отношусь к "начитавшимся классов", но чего ж об один камень 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:
Вроде рабочий вариант с многими писателями  ;)

Функции:
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.
1) Ну "только один писатель" вполне usable, а вот без pending (запрос на запись) прожить трудно.
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? Как известно в математике и некоторых дугих религиях, задача станет ещё проще, если отказаться от задачи. А именно, реализовать обычный, а не рв-лок, например так:

Код
C++ (Qt)
struct mutex
{
  atomic_int n = 0;
  int level = 1;
 
  lock ()
  {
      int nn = ++n; //atomic pre increment
      while (nn != level);
  }
 
  unlock ()
  {
     ++level; //non-atomic, but protected by our newborn mutex!
  }
};
 

понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать  8)


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: Igors от Март 31, 2011, 07:08
Кстати, зачем write_pending, почему нельзя просто ставить write (как у Akon, только CAS вместо fetchAndXor)? Ну то есть чтобы читатели не подвесили писателя, как я понял понял, но вроде бы одного write достаточно и короче получится, нет?
Писателя не повесят, но могут сильно затормозить (часто читающих заметно больше). Хотя может я не совсем Вас понял.

Код
C++ (Qt)
struct mutex
{
  atomic_int n = 0;
  int level = 1;
 
  lock ()
  {
      int nn = ++n; //atomic pre increment
      while (nn != level);
  }
 
  unlock ()
  {
     level = nn + 1; //non-atomic, but protected by our newborn mutex!
  }
};
 
Так это махровый семафор, только непонятно до каких пор растет счетчик. Через CAS приятнее
Код
C++ (Qt)
struct semaphore
{
  volatile int n;
 
  semaphore( int _n ) : n(_n) {}
 
  void acquire( void )
  {
      while (true) {   // нагреваем процессор...
       int value = n;
       if (value && CompareAndSwap(&n, value, value - 1)) break;
      }
  }
 
  void release( void )
  {
    ++n;
  }
};
 


Название: 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? Как известно в математике и некоторых дугих религиях, задача станет ещё проще, если отказаться от задачи. А именно, реализовать обычный, а не рв-лок, например так:

Код
C++ (Qt)
struct mutex
{
  atomic_int n = 0;
  int level = 1;
 
  lock ()
  {
      int nn = ++n; //atomic pre increment
      while (nn != level);
  }
 
  unlock ()
  {
     level = nn + 1; //non-atomic, but protected by our newborn mutex!
  }
};
 

понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать  8)
А в функции unlock,  nn - это что? Если это тот atomic_int n, то тогда такая ситуация:
Первый поток вызывает lock (n = 1, level = 1), но до unlock ещё не успевает дойти. В этот момент другой поток вызывает lock (n = 2, level = 1). Соответственно он не проходит и крутится в цикле. Затем, первый поток вызывает unlock (level = 2+1 = 3), что не освобождает второй поток, который продолжает крутится в цикле.    
Лучше изменить unlock так:
Код
C++ (Qt)
void unlock ()
{
   level++;
}
 


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Март 31, 2011, 14:17
Цитировать
понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать  Крутой
Хорошо, предположим, что у нас есть такой мьютекс (реализованный без CAS) И как с помощью него сделать lock-free?
Вот пример самого мьютекса:
Код
C++ (Qt)
class mutex
{
  atomic_int key = -1;
public:
  lock ()
  {
      int tmp = 0;
      do {
         tmp = ++key;
         if (tmp) key--;
     } while (tmp != 0);
  }
 
  unlock ()
  {
     key = -1;
  }
};
 


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Апрель 01, 2011, 00:17
А в функции unlock,  nn - это что?

это опечатался. На самом деле level++, никакого nn нет. Igors, счётчик врапится бесконечно, важно, чтобы число тредов одновременно ждущих мьютекса не превысило диапазон счётчика. Понятно, что 4e9 тредов одновременно работать не могут.

Edit: не совсем понял про "махровый семафор", у меня всегда 1 захвативший, т.е. это мьютекс.

Что CAS лучше, то тут спорить не о чем, конечно он и эффективнее и гибче. Просто интересно было обойтись ++, тем более, что спинлок несложный примитив. Правда эффективной реализации не получилось.

m_ax имея обычный мьютекс можно реализовать rw-мьютекс так:

Код
C++ (Qt)
struct rw_lock
{
  bool m_writer = false;
  int m_readers = 0;
  mutex m_mutex;
 
  lock_read ()
  {
     while (true)
     {
         scoped_lock sl (m_mutex);
         if (!m_writer)
         {
            ++m_readers;
            return;
         }
     }
  }
 
  unlock_read ()
  {
     scoped_lock sl (m_mutex);
     --m_readers;
  }
 
  lock_write ()
  {
     while (true)
     {
         scoped_lock sl (m_mutex);
         if (!m_readers && !m_writer)
         {
            m_writer = true;
            return;
         }
     }
  }
 
  unlock_write ()
  {
     scoped_lock sl (m_mutex);
     m_write = false;
  }
};
 

Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность:

T1, T2 пришли, захватили ключи 0 и 1, key = 1
T1 отработал и ушёл, key = -1
T2 решил сделать --key, и завис навсегда (между key = -1 и -2)


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: Igors от Апрель 01, 2011, 10:05
Код
C++ (Qt)
struct mutex
{
  atomic_int n = 0;
  int level = 1;
 
  lock ()
  {
      int nn = ++n; //atomic pre increment
      while (nn != level);
  }
 
  unlock ()
  {
     ++level; //non-atomic, but protected by our newborn mutex!
  }
};
 
3 нитки сделали lock(), n = 3. level = 1. Одна из ниток проскочила, 2 остальных крутят while. Затем проскочившая вызывает unlock(), но это не разблокирует мутекс (n = 3. level = 2).

Мне кажется тема зашла в тупик, на мой взгляд гораздо лучше/полезнее было бы обсудить вот это
В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев.
Конечно в новой теме


Название: 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
Цитировать
Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность:

T1, T2 пришли, захватили ключи 0 и 1, key = 1
T1 отработал и ушёл, key = -1
T2 решил сделать --key, и завис навсегда (между key = -1 и -2)
Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0

Цитировать
lock_read ()
   {
      while (true)
      {
          scoped_lock sl (m_mutex);
          if (!m_writer)
          {
             ++m_readers;
             return;
          }
      }
   }
А что такое scoped_lock ? И как он устроен?


Название: Re: AtomicData или механизм атомарной операции записl
Отправлено: m_ax от Апрель 01, 2011, 12:03


Мне кажется тема зашла в тупик, на мой взгляд гораздо лучше/полезнее было бы обсудить вот это
В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев.
Конечно в новой теме

Давайте создадим, как предлагаете назвать? И ещё для этого, если использовать только абстрактные rwlocker нужно мне бы хотелось знать логику их работы. 


Название: 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, ну и что? Следующий виток цикла:
tmp = ++key;
tmp == -1; key == -1;
условие if (tmp) - не выполняется и как следствие нет и key--;
переходим к следующему витку.
Или я всё же не прав?
Ну как же не выполняется: if (-1) возвращает true  :)


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: m_ax от Апрель 01, 2011, 13:05
Цитировать
Ну как же не выполняется: if (-1) возвращает true  Улыбающийся
Ой, совсем я уже того))
Разумеется нужно
if (tmp > 0) --key;


Название: Re: AtomicData или механизм атомарной операции записи данных
Отправлено: brankovic от Апрель 01, 2011, 14:07
Цитировать
Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность:

T1, T2 пришли, захватили ключи 0 и 1, key = 1
T1 отработал и ушёл, key = -1
T2 решил сделать --key, и завис навсегда (между key = -1 и -2)
Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0

> 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
Цитировать
Про Ваш мьютекс. Там опять присвоение, и оно опять компрометирует атомарность:

T1, T2 пришли, захватили ключи 0 и 1, key = 1
T1 отработал и ушёл, key = -1
T2 решил сделать --key, и завис навсегда (между key = -1 и -2)
Нет, не зависнет он навсегда, поскольку key-- будет только тогда, когда tmp > 0

> 0 или != 0 неважно, ещё раз подробнее:

T1, T2 пришли, захватили ключи 0 и 1, key = 1
T2 приготовился сделать --key
T2 заснул
T1 отработал и ушёл, key = -1
T2 проснулся, сделал --key, и завис навсегда между key = -1 и -2
Прошу прощения, но я Вам не верю)
Вот как я это вижу (поэтапно):
Код
C++ (Qt)
void lock() {
       int key = 0;
       do {
           key = ++m_key;
           if (key > 0) m_key--;
       } while (key != 0);
   }
 
   void unlock() {
       m_key = -1;
   }
 
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.
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) - Не выполняется! Начинает писать данные.
Где ошибка??
Позвольте спросить: ну а на <..> создавать конструкцию которая может потребовать столь "углубленного анализа"?  :)  Это что, практично? Профессионально? Попробуйте один раз применить CAS - и, уверяю Вас, все проблемы исчезнут  :)


Название: 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 - и, уверяю Вас, все проблемы исчезнут  Улыбающийся
И опять-таки согласен)

Короче, убедили)