Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: Кодт Россия  
Дата: 02.04.15 20:48
Оценка: -2
Здравствуйте, rsdn_179b, Вы писали:

_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?


1) Если есть несколько точек ожидания одной и той же CV — будь то один поток, интересующийся разными вещами, или несколько потоков, ворующих посылки друг у друга из-под носа
2) Если условие может меняться туда-сюда быстрее, чем заинтересованный в нём поток

Пример:
mutex m;
conditional_variable cv;
DWORD flags;

void update(DWORD set, DWORD reset)
{
  scoped_lock l(m);
  DWORD newflags = (flags & ~reset) | set;
  // порядок извещения и изменения не важен, мы же ещё не отпустили мьютекс
  if(flags != newflags) cv.notify_one();
  flags = newflags;
}

void expect(DWORD flag, bool isset)
{
  unique_lock<mutex> l(m);
  while(bool(flags & flag) != isset) cv.wait(l);
}

Держать по отдельной CV на каждый бит — расточительно; цена экономии — ложные пробуждения, если поток-источник выставил не тот бит, который нам интересен.

Ещё пример
volatile unsigned ready_flag;

void update(bool flag)
{
  ::InterlockedExchange(&ready_flag, flag); // запись атомарная, лочить незачем
  cv.notify_one(); // но известить потребителя нужно
  // если поменяем на notify_all для второго сценария, - ничего не изменится
}

void take_ready() // ждёт ready_flag и опускает его
{
  unique_lock<mutex> l(m); // чисто для протокола - так требует CV
  while(!::InterlockedCompareExchange(&ready_flag,false,true)) cv.wait(l);
}

// первый сценарий
void update_nothing() { update(true); update(false); } // дёрнули, даже дважды, но пока поток-приёмник проснётся...

// второй сценарий
void update_twice() { update(true); update(true); } // дёрнули дважды
void thread_one()   { take_ready(); } // первый проснулся и погасил
void thread_two()   { take_ready(); } // второй проснулся, а уже погашено


_>Зачем крутиться в цикле и проверять какие-то условия


Можно и не крутиться, а один раз проверить, обломиться и бросить это занятие.

CV — это способ извещения о событии, о неком моменте, в котором, возможно, выполнилось нужное условие.
Говоря в терминах WinAPI, это PulseEvent.
Связывать CV с состоянием и экономить на проверках и булевских флажках — можно в некоторых узких рамках. (В отличие от Event, которое и событие, и булев флажок, или от семафора, который и событие, и счётчик).
Мне кажется, именно эта особенность WinAPI приучила программистов к иным сценариям синхронизации, чем во всём остальном мире. И то, проблема ложных пробуждений там тоже существует.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.