Russian Qt Forum
Май 15, 2024, 22:25 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 ... 4 5 [6] 7   Вниз
  Печать  
Автор Тема: AtomicData или механизм атомарной операции записи данных  (Прочитано 43732 раз)
Akon
Гость
« Ответ #75 : Март 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
думаю всем понятен.
Записан
vregess
Гость
« Ответ #76 : Март 30, 2011, 08:46 »

m_ax, а ты пишешь тесты для своего алгоритма?
Может стоит протестировать реализацию сперва - сразу сократишь количество постов в теме.
Да и народ не будет мучиться описывая вероятные исходы.

Возьми boost.test или googletest или что-нить еще..
Заодно и производительность сравнить можно будет с обычным rw подходом.
м?)
Записан
Akon
Гость
« Ответ #77 : Март 30, 2011, 09:08 »

Нужно увеличивать скважность импульса, например, отказаться от остатка кванта

Код:
...
while (m_state.fetchAndOr(WriterMask) & ReadersMask) {  // readers presence
m_state.fetchAndXor(WriterMask);  // remove writer presence to prevent deadlock
sleep(0);
}
...
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #78 : Март 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 надо найти в хедере для своей платформы
« Последнее редактирование: Март 30, 2011, 10:25 от Igors » Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #79 : Март 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 который предназначен для решения таких коллизий? Зачем рыть себе яму привлекая вторую переменную за которой надо (мучительно) следить?

Да, Вы правы. Пора завязывать с этой темой)
Всем спасибо за обсуждение)
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Akon
Гость
« Ответ #80 : Март 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.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #81 : Март 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"

При всем этом: относительно недавно столкнулся с ситуацией когда даже этот локер (увы) тормозит - уж очень велика интенсивность  Улыбающийся
Записан
brankovic
Гость
« Ответ #82 : Март 31, 2011, 01:01 »

Нужно увеличивать скважность импульса, например, отказаться от остатка кванта
Шокированный кажется я понимаю! ..да нет, кого я обманываю.. Смеющийся

Но интересный вариант Вы описали, правда смущает ограничение на одного пишущего и завис писателя, если поток читателей постоянный. Но в реализации подкопаться не к чему (кроме опечатки Подмигивающий. А работает это потому, что каждая атомарная переменная используется только в атомарных операциях, а 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!
  }
};
 

понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать  Крутой
« Последнее редактирование: Апрель 01, 2011, 00:20 от brankovic » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #83 : Март 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;
  }
};
 
« Последнее редактирование: Март 31, 2011, 07:10 от Igors » Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #84 : Март 31, 2011, 11:03 »

Нужно увеличивать скважность импульса, например, отказаться от остатка кванта
Шокированный кажется я понимаю! ..да нет, кого я обманываю.. Смеющийся

Но интересный вариант Вы описали, правда смущает ограничение на одного пишущего и завис писателя, если поток читателей постоянный. Но в реализации подкопаться не к чему (кроме опечатки Подмигивающий. А работает это потому, что каждая атомарная переменная используется только в атомарных операциях, а 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!
  }
};
 

понятно, что имея настоящий мьютекс можно реализовать всё, что угодно. На этом этапе математики зачехляют линейки, а программисты идут писать тесты. Правда код я не проверял, сегодня только придумал вечером, так что критику интересно бы услышать  Крутой
А в функции 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++;
}
 
« Последнее редактирование: Март 31, 2011, 13:35 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #85 : Март 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;
  }
};
 
« Последнее редактирование: Март 31, 2011, 14:40 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
brankovic
Гость
« Ответ #86 : Апрель 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)
« Последнее редактирование: Апрель 01, 2011, 00:25 от brankovic » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #87 : Апрель 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).

Мне кажется тема зашла в тупик, на мой взгляд гораздо лучше/полезнее было бы обсудить вот это
В смысле, можно создать внутренний буфер (массив тобиш). Пока читатели читают из одной ячейки массива, писатель, чтоб не простаивать пишет в следующую, и после того как запишет устанавливает индекс для чтения с актуальными данными. При этом в следующий раз писатель будет выбирать новый индекс для записи с учётом того, откуда читают данные читатели. Короче, читатели всегда будут знать где актуальные даные, а писатели будут знать куда писать, чтобы не было простоев.
Конечно в новой теме
Записан
brankovic
Гость
« Ответ #88 : Апрель 01, 2011, 11:55 »

3 нитки сделали lock(), n = 3. level = 1. Одна из ниток проскочила, 2 остальных крутят while. Затем проскочившая вызывает unlock(), но это не разблокирует мутекс (n = 3. level = 2).

2й тред будет иметь в стеке nn == 2 и получит право на лок, как только level станет 2
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #89 : Апрель 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 ? И как он устроен?
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Страниц: 1 ... 4 5 [6] 7   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.563 секунд. Запросов: 22.