std::condition_variable счетчик notify
От: Selavi  
Дата: 16.06.15 21:18
Оценка:
Не удалось нагуглить...

есть код:

std::condition_variable cv;
std::unique_lock<std::mutex> lock(mutex);

while (!terminated)
{
  cv.wait(lock); //1
  {
    //2
  }
}


Если вызвать откуда то cv.notify(), то отработает 2 и все ок. Но если вызвать еще раз cv.notify() во время 2, то вызов пропадет зря и в момент, когда выполнение вернется в 1, то поток остановится и будет ждать cv.notify

Можно, конечно, реализовывать вручную счетчики, но может есть какой нить изящный способ сделать так, чтобы 2 отрабатывало столько раз, сколько будет вызван notify?

Спасибо)
Re: std::condition_variable счетчик notify
От: Evgeny.Panasyuk Россия  
Дата: 16.06.15 21:56
Оценка:
Здравствуйте, Selavi, Вы писали:

S>Если вызвать откуда то cv.notify(), то отработает 2 и все ок. Но если вызвать еще раз cv.notify() во время 2, то вызов пропадет зря и в момент, когда выполнение вернется в 1, то поток остановится и будет ждать cv.notify


Изменение условия, для ожидания которого используется CV, должно происходить под тем же мьютексом.

S>Можно, конечно, реализовывать вручную счетчики, но может есть какой нить изящный способ сделать так, чтобы 2 отрабатывало столько раз, сколько будет вызван notify?


Для этого дополнительно нужно реализовывать защиту от spurious wakeup.
Re: std::condition_variable счетчик notify
От: vpchelko  
Дата: 16.06.15 22:06
Оценка:
Здравствуйте, Selavi, Вы писали:

  Скрытый текст
S>Не удалось нагуглить...

S>есть код:


S>
S>std::condition_variable cv;
S>std::unique_lock<std::mutex> lock(mutex);

S>while (!terminated)
S>{
S>  cv.wait(lock); //1
S>  {
S>    //2
S>  }
S>}
S>


S>Если вызвать откуда то cv.notify(), то отработает 2 и все ок. Но если вызвать еще раз cv.notify() во время 2, то вызов пропадет зря и в момент, когда выполнение вернется в 1, то поток остановится и будет ждать cv.notify


S>Можно, конечно, реализовывать вручную счетчики, но может есть какой нить изящный способ сделать так, чтобы 2 отрабатывало столько раз, сколько будет вызван notify?


S>Спасибо)


По сути у тебя получается, типичный single threaded executor service — и ты в него добавляешь, только одну и туже задачу. Может не стоит городить костыли, а немного по другому подойти к задаче?
Сало Украине, Героям Сала
Отредактировано 16.06.2015 22:07 vpchelko . Предыдущая версия .
Re[2]: std::condition_variable счетчик notify
От: Selavi  
Дата: 16.06.15 22:10
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, Selavi, Вы писали:


S>>Если вызвать откуда то cv.notify(), то отработает 2 и все ок. Но если вызвать еще раз cv.notify() во время 2, то вызов пропадет зря и в момент, когда выполнение вернется в 1, то поток остановится и будет ждать cv.notify


EP>Изменение условия, для ожидания которого используется CV, должно происходить под тем же мьютексом.


Значит wait отпускает мютекс, а после notify() захватывает снова и следующий notify() будет ждать, пока управление вновь не вернется к wait?
Мне это не нравится, поскольку уведомляющим потокам придется ждать пока отработает уведомляемый.

А хотелось бы, чтобы уведомляющий поток кинул notify() и занялся своими делами.

Нет ли уже готового стандартного способа?
Re[2]: std::condition_variable счетчик notify
От: vpchelko  
Дата: 16.06.15 22:14
Оценка: -1
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Изменение условия, для ожидания которого используется CV, должно происходить под тем же мьютексом.


Вообще-то необязательно, если для изменения и условия использовать атомарную операцию — типа CAS, такое вроде давно используется в lock-free алгоритмах. Готового должно быть полно.

EP>Для этого дополнительно нужно реализовывать защиту от spurious wakeup.


Ну это же обычная проверка условия.

Т.е. получается типа:

cv.wait()

while(atomic_condition) {
// 2
}
Сало Украине, Героям Сала
Отредактировано 16.06.2015 22:35 vpchelko . Предыдущая версия . Еще …
Отредактировано 16.06.2015 22:27 vpchelko . Предыдущая версия .
Отредактировано 16.06.2015 22:18 vpchelko . Предыдущая версия .
Отредактировано 16.06.2015 22:17 vpchelko . Предыдущая версия .
Re: std::condition_variable счетчик notify
От: Vamp Россия  
Дата: 16.06.15 22:15
Оценка: :)
Ты неправильно используешь условную переменную. Это не сигнал, а переменная. А у тебя нету переменной! Оттого и проблемы.
S>Не удалось нагуглить...

S>есть код:


S>[ccode]

S>std::condition_variable cv;
S>std::unique_lock<std::mutex> lock(mutex);

S>while (!terminated)

S>{
S> cv.wait(lock); //1
S> {
S> //2
S>
Да здравствует мыло душистое и веревка пушистая.
Re: std::condition_variable счетчик notify
От: andyp  
Дата: 16.06.15 22:33
Оценка: +1
Здравствуйте, Selavi, Вы писали:

S>Можно, конечно, реализовывать вручную счетчики, но может есть какой нить изящный способ сделать так, чтобы 2 отрабатывало столько раз, сколько будет вызван notify?


Нет. condition variable так не работает. Счетчик и есть изящный способ (он собственно и есть shared state твоих ниток). Инкрементируй его под тем же мьютексом, что ты используешь при ожидании. В цикле каждый раз крутись пока счетчик нулевой



void signal()
{    
   {
     std::unique_lock<std::mutex> lock(mutex);
     counter++;    
   }
   cv.signal();
}


//где-то в твоем коде...
{
   std::unique_lock<std::mutex> lock(mutex);
   while(!counter)
  {
    cv.wait(lock);
  }

  //делай что тебе надо и декрементируй счетчик
  counter--;
}
Отредактировано 16.06.2015 22:39 andyp . Предыдущая версия . Еще …
Отредактировано 16.06.2015 22:35 andyp . Предыдущая версия .
Re[2]: std::condition_variable счетчик notify
От: vpchelko  
Дата: 16.06.15 22:36
Оценка: -1
Здравствуйте, andyp, Вы писали:

  Скрытый текст
A>Здравствуйте, Selavi, Вы писали:

S>>Можно, конечно, реализовывать вручную счетчики, но может есть какой нить изящный способ сделать так, чтобы 2 отрабатывало столько раз, сколько будет вызван notify?


A>Нет. condition variable так не работает. Счетчик и есть изящный способ (он собственно и есть shared state твоих ниток). Инкрементируй его под тем же мьютексом, что ты используешь при ожидании. В цикле каждый раз крутись пока счетчик нулевой



A>

A>void signal()
A>{    
A>   {
A>     std::lock_guard<std::mutex> lock(mutex);
A>     counter++;    
A>   }
A>   cv.signal();
A>}


A>//где-то в твоем коде...
A>{
A>   std::lock_guard<std::mutex> lock(mutex);
A>   while(!counter)
A>  {
A>    cv.wait();
A>  }

A>  //делай что тебе надо и декрементируй счетчик
A>  counter--;
A>}
A>


Здесь mutex ненужен
Вы лепите блокировку — там где без неё много лет прекрасно справляются.
Сало Украине, Героям Сала
Отредактировано 16.06.2015 22:40 vpchelko . Предыдущая версия .
Re[3]: std::condition_variable счетчик notify
От: andyp  
Дата: 16.06.15 22:40
Оценка:
Здравствуйте, vpchelko, Вы писали:

V>Здесь mutex ненужен


Где? Могу и накосячить Не использую пока threading library.
Re[2]: std::condition_variable счетчик notify
От: Selavi  
Дата: 16.06.15 22:43
Оценка:
Здравствуйте, andyp, Вы писали:

  скрыто
A>Здравствуйте, Selavi, Вы писали:

S>>Можно, конечно, реализовывать вручную счетчики, но может есть какой нить изящный способ сделать так, чтобы 2 отрабатывало столько раз, сколько будет вызван notify?


A>Нет. condition variable так не работает. Счетчик и есть изящный способ (он собственно и есть shared state твоих ниток). Инкрементируй его под тем же мьютексом, что ты используешь при ожидании. В цикле каждый раз крутись пока счетчик нулевой



A>

A>void signal()
A>{    
A>   {
A>     std::unique_lock<std::mutex> lock(mutex);
A>     counter++;    
A>   }
A>   cv.signal();
A>}


A>//где-то в твоем коде...
A>{
A>   std::unique_lock<std::mutex> lock(mutex);
A>   while(!counter)
A>  {
A>    cv.wait(lock);
A>  }

A>  //делай что тебе надо и декрементируй счетчик
A>  counter--;
A>}
A>


Не нравится мне этот код
Какой смысл в счетчике, если мы все равно ждем мютекса, чтобы его увеличить?
Re[3]: std::condition_variable счетчик notify
От: andyp  
Дата: 16.06.15 22:48
Оценка:
Здравствуйте, Selavi, Вы писали:

S>Какой смысл в счетчике, если мы все равно ждем мютекса, чтобы его увеличить?


Счетчик shared. Надо ждать. Иначе будут гонки с декрементом. Но проблема на мой взгляд невелика — ждем только в ветке с декрементом. cv.wait отдает мьютекс
Re[3]: std::condition_variable счетчик notify
От: Evgeny.Panasyuk Россия  
Дата: 16.06.15 22:58
Оценка:
Здравствуйте, vpchelko, Вы писали:

EP>>Изменение условия, для ожидания которого используется CV, должно происходить под тем же мьютексом.

V>Вообще-то необязательно, если для изменения и условия использовать атомарную операцию — типа CAS, такое вроде давно используется в lock-free алгоритмах.

В таких случаях потеря уведомления обычно не страшна (которая получается из-за изменения состояния без мьютекса), так как используется wait_for с таймаутом, вместо wait.
В крайнем случае notify_one можно делать под мьютексом, но тогда смысла в lock free может и не быть.

V>Готового должно быть полно.


Готового чего?

EP>>Для этого дополнительно нужно реализовывать защиту от spurious wakeup.

V>Ну это же обычная проверка условия.

Которой нет в исходном сообщении ТС
Отредактировано 16.06.2015 23:36 Evgeny.Panasyuk . Предыдущая версия . Еще …
Отредактировано 16.06.2015 23:36 Evgeny.Panasyuk . Предыдущая версия .
Re[4]: std::condition_variable счетчик notify
От: Selavi  
Дата: 16.06.15 23:04
Оценка:
Здравствуйте, andyp, Вы писали:

A>Здравствуйте, Selavi, Вы писали:


S>>Какой смысл в счетчике, если мы все равно ждем мютекса, чтобы его увеличить?


A>Счетчик shared. Надо ждать. Иначе будут гонки с декрементом. Но проблема на мой взгляд невелика — ждем только в ветке с декрементом. cv.wait отдает мьютекс


В таком варианте вообще не нужен счетчик

мы ждем пока отработает задача, чтобы увеличить счетчик...зачем он вообще нужен? Проще сразу делать cv.notify() для запуска очередного рабочего цикла..


Я хочу, чтоб было так:

1) рабочий поток ждет
2) вызывающий поток(1) кидает уведомление
3) рабочий поток начинает выполнять задачу
4) вызывающий поток(1-N) кидает уведомление во время выполнения задачи и выходит из метода уведомления
5) рабочий поток завершает задачу
6) рабочий поток видит, что его жду новые уведомления и для каждого запускает новый рабочий цикл, либо переходит в ждущий режим если нет уведомлений

как вариант — это очередь заданий.
но я надеялся, что есть некий стандартный механизм...
Re[5]: std::condition_variable счетчик notify
От: andyp  
Дата: 16.06.15 23:15
Оценка: +1
Здравствуйте, Selavi, Вы писали:

S>В таком варианте вообще не нужен счетчик


Счетчик нужен, чтобы считать, сколько раз ты позвал cv.signal().

S>мы ждем пока отработает задача, чтобы увеличить счетчик...зачем он вообще нужен? Проще сразу делать cv.notify() для запуска очередного рабочего цикла..


Зачем ждать? Декрементируй счетчик, отдай мьютекс, продолжай заниматься своими делами. Мьютекс отдают после того, как закончили работать с shared state.

S>Я хочу, чтоб было так:


S>1) рабочий поток ждет

S>2) вызывающий поток(1) кидает уведомление
S>3) рабочий поток начинает выполнять задачу
S>4) вызывающий поток(1-N) кидает уведомление во время выполнения задачи и выходит из метода уведомления
S>5) рабочий поток завершает задачу
S>6) рабочий поток видит, что его жду новые уведомления и для каждого запускает новый рабочий цикл, либо переходит в ждущий режим если нет уведомлений

S>как вариант — это очередь заданий.


Очередь — все тоже самое, только shared state будет объект std::queue. Вместо инкремента будет std::queue.push(), std::queue.pop() — декремент, в условии while(queue.empty()

S>но я надеялся, что есть некий стандартный механизм...


Это и есть стнадартный механизм работы с кондварами
Re[3]: std::condition_variable счетчик notify
От: Evgeny.Panasyuk Россия  
Дата: 16.06.15 23:26
Оценка: +2
Здравствуйте, Selavi, Вы писали:

S>>>Если вызвать откуда то cv.notify(), то отработает 2 и все ок. Но если вызвать еще раз cv.notify() во время 2, то вызов пропадет зря и в момент, когда выполнение вернется в 1, то поток остановится и будет ждать cv.notify

EP>>Изменение условия, для ожидания которого используется CV, должно происходить под тем же мьютексом.
S>Значит wait отпускает мютекс, а после notify() захватывает снова и следующий notify() будет ждать, пока управление вновь не вернется к wait?

Будет ждать не notify, а следующее изменение состояния. notify можно делать после изменения состояния, не под мьютексом.

S>Мне это не нравится, поскольку уведомляющим потокам придется ждать пока отработает уведомляемый.

S>А хотелось бы, чтобы уведомляющий поток кинул notify() и занялся своими делами.
S>Нет ли уже готового стандартного способа?

Если нужно посчитать именно количество вызовов notify, то нужно завести счётчик, чтобы отфильтровать spurious wakeup.
Если при этом требуется не захватывать мьютекс на стороне уведомляющего — то нужно использовать атомарный счётчик, плюс вместо wait нужен wait_for с таймаутом, так как помимо spurious wakeup теперь возможны потерянные уведомления (между проверкой условия и wait'ом, условие может поменяться, так как оно теперь не под мьютексом).
Re[4]: std::condition_variable счетчик notify
От: uzhas Ниоткуда  
Дата: 17.06.15 07:57
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>плюс вместо wait нужен wait_for с таймаутом, так как помимо spurious wakeup теперь возможны потерянные уведомления (между проверкой условия и wait'ом, условие может поменяться, так как оно теперь не под мьютексом).


хороший воркараунд ) но все же лучше учить всех использовать cv в паре с mutex, даже если условие допускает атомарный доступ. что я и советую сделать TC.
Re[5]: std::condition_variable счетчик notify
От: Selavi  
Дата: 17.06.15 09:03
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, Evgeny.Panasyuk, Вы писали:


EP>>плюс вместо wait нужен wait_for с таймаутом, так как помимо spurious wakeup теперь возможны потерянные уведомления (между проверкой условия и wait'ом, условие может поменяться, так как оно теперь не под мьютексом).


U>хороший воркараунд ) но все же лучше учить всех использовать cv в паре с mutex, даже если условие допускает атомарный доступ. что я и советую сделать TC.


ну тогда видимо нужно вешать счетчик на другой мютекс, чтобы уведомляющий поток не ждал, пока отработается задача в рабочем потоке.
Re[6]: std::condition_variable счетчик notify
От: uzhas Ниоткуда  
Дата: 17.06.15 09:20
Оценка: +2
Здравствуйте, Selavi, Вы писали:

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


конечно, вам нужно отпускать мьютекс как можно быстрее. тогда нотификация будет пролетать быстро
длинные задачи делайте без участия этого мьютекса (отпустите его)
только когда вы закончите делать задачу вам надо будет уменьшить счетчик — делайте это аккуратно, т.к.
1) менять счетчик надо под мьютексом
2) если счетчик после уменьшения все еще положительный, то надо запустить следующую задачу без ожидания на cv
3) если счетчик нулевой, то, не отпуская мьютекс, надо уйти в cv.wait(mutex) =). отпустить, конечно, можно, но потом надо где-то снова его залочить, проверить счетчик и, если тот нулевой, уйти в cv.wait(mutex); иначе приступить к след. задаче

успехов
Отредактировано 17.06.2015 9:25 uzhas . Предыдущая версия .
Re[5]: std::condition_variable счетчик notify
От: Evgeny.Panasyuk Россия  
Дата: 17.06.15 11:00
Оценка:
Здравствуйте, uzhas, Вы писали:

EP>>плюс вместо wait нужен wait_for с таймаутом, так как помимо spurious wakeup теперь возможны потерянные уведомления (между проверкой условия и wait'ом, условие может поменяться, так как оно теперь не под мьютексом).

U>хороший воркараунд ) но все же лучше учить всех использовать cv в паре с mutex, даже если условие допускает атомарный доступ. что я и советую сделать TC.

Изменение условия может быть долгим, например если это какое-нибудь STM. В этом случае можно под мьютексом делать только notify_one — тогда пропущенных уведомлений не будет.
Есть ещё вариант без CV — просто sleep_for/yield + атомарное изменение состояния/счётчика.

У каждого варианта какие-то свои преимущества/недостатки. Например в случае изменения счётчика/состояния под мьютексом — уведомляемый поток может просыпаться очень редко, практически только на уведомлениях (+ редкие spurious wakeup).
В случае же с wait_for/sleep_for — получаем либо слишком частые просыпания, либо возможность нарваться на слишком долгую реакцию при пропущенном уведомлении.
Re[3]: std::condition_variable счетчик notify
От: B0FEE664  
Дата: 17.06.15 11:22
Оценка:
Здравствуйте, Selavi, Вы писали:

S>А хотелось бы, чтобы уведомляющий поток кинул notify() и занялся своими делами.


S>Нет ли уже готового стандартного способа?

Стандартного — нет. Посмотрите сюда
Автор: B0FEE664
Дата: 29.09.14
.
И каждый день — без права на ошибку...
Re[6]: std::condition_variable счетчик notify
От: uzhas Ниоткуда  
Дата: 17.06.15 11:36
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Изменение условия может быть долгим,

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

EP>например если это какое-нибудь STM.

тут я не понял переход к STM. не проще ли было привести vector::push_back как пример изменения условия? или ты это как-то увязал с инкрементом счетчика?

EP>В этом случае можно под мьютексом делать только notify_one — тогда пропущенных уведомлений не будет.


поясни какую разницу в семантике ты видишь при вызове notify_one под мьютексом и вне мьютекса? у тебя есть некие ожидания как cv откликнется на notify_one? есть гарантии со стороны C++ стандарта и\или POSIX?
Re[7]: std::condition_variable счетчик notify
От: Evgeny.Panasyuk Россия  
Дата: 17.06.15 12:01
Оценка: 26 (2)
Здравствуйте, uzhas, Вы писали:

EP>>например если это какое-нибудь STM.

U>тут я не понял переход к STM. не проще ли было привести vector::push_back как пример изменения условия? или ты это как-то увязал с инкрементом счетчика?

Я имею в виду случай когда есть атомарные операции, которые не нужно защищать муьютексом (например push для lock-free queue), но эффект от которых ждут по CV.

EP>>В этом случае можно под мьютексом делать только notify_one — тогда пропущенных уведомлений не будет.

U>поясни какую разницу в семантике ты видишь при вызове notify_one под мьютексом и вне мьютекса?

Это разница появляется в случае когда само состояние меняется не под мьютексом (например потому что оно атомарное).
То есть если какой-то поток сделал ++atomic_counter не под мьютексом, потом под мьютексом сделал notify_one — то поток находящийся на wait не пропустит это уведомление.
Если же и ++atomic_counter и notify_one находятся не под мьютексом, то возможна потеря уведомления:
condition_variable cv;
mutex m;
atomic<unsigned> counter = 0;
// ...
unique_lock<mutex> ul(m);
while(!counter)
{
    // <--- meanwhile in other thread: ++counter; cv.notify_one();
    // as the result notification is lost
    cv.wait(ul);
}
Если бы хотя бы notify_one было под мьютексом — то уведомление не потерялось бы.

U>у тебя есть некие ожидания как cv откликнется на notify_one? есть гарантии со стороны C++ стандарта и\или POSIX?


Если один поток сидит на wait — то notify_one должен его разбудить (точнее по ISO — unblock).
Отредактировано 17.06.2015 12:02 Evgeny.Panasyuk . Предыдущая версия .
Re[8]: std::condition_variable счетчик notify
От: uzhas Ниоткуда  
Дата: 17.06.15 16:15
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Это разница появляется в случае когда само состояние меняется не под мьютексом (например потому что оно атомарное).

EP>То есть если какой-то поток сделал ++atomic_counter не под мьютексом, потом под мьютексом сделал notify_one — то поток находящийся на wait не пропустит это уведомление.
аа, теперь понял о чем ты
сразу натолкнуло на вопрос: что происходит, если cv получил сигнал (кто-то позвал notify*()), но не может сделать reacquire lock, потому что мьютекс залочен в другом потоке?
то же самое про wait_for с таймаутом
читаем доку

30.5.1 Class condition_variable [thread.condition.condvar]
.....
template <class Rep, class Period>
cv_status wait_for(unique_lock<mutex>& lock, const chrono::duration<Rep, Period>& rel_time);

24 Requires: lock is locked by the calling thread, and either
— no other thread is waiting on this condition_variable object or
— lock.mutex() returns the same value for each of the lock arguments supplied by all concurrently waiting (via wait, wait_for, or wait_until) threads.
25 Effects:
— Atomically calls lock.unlock() and blocks on *this.
— When unblocked, calls lock.lock() (possibly blocking on the lock), then returns.
— The function will unblock when signaled by a call to notify_one() or a call to notify_all(),
by the elapsed time rel_time passing (30.2.4), or spuriously.
— If the function exits via an exception, lock.lock() shall be called prior to exiting the function scope.
26 Returns: cv_status::timeout if the function unblocked because rel_time elapsed, otherwise cv_status::no_timeout.


из чего мы делаем вывод, в wait_for мы можем повиснуть на бесконечно долго, даже если задали период времени в 10 секунд
далее мы можем уже позвать wait в другом потоке либо с локом того же мьютекса, либо со вторым мьютексом..тут мы закипаем

EP>Если один поток сидит на wait — то notify_one должен его разбудить

я телепатировал в неверном направлении, сорри
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.