condition_variable.notify_all
От: Barbar1an Украина  
Дата: 20.07.20 11:29
Оценка:
я так и не понял из доков, если notify_all вызван до того как ожидатель вызвал wait , то что мы получим?
дедлок?

если дедлок то что это за фуфло такое? на чистом винапи можно легко избежать этой проблемы, потому что там если эвент устанорвлен, то Wait не будет ждать.
Я изъездил эту страну вдоль и поперек, общался с умнейшими людьми и я могу вам ручаться в том, что обработка данных является лишь причудой, мода на которую продержится не более года. (с) Эксперт, авторитет и профессионал из 1957 г.
Re: condition_variable.notify_all
От: qaz77  
Дата: 20.07.20 11:42
Оценка: +1
Здравствуйте, Barbar1an, Вы писали:

B>я так и не понял из доков, если notify_all вызван до того как ожидатель вызвал wait , то что мы получим?

B>дедлок?

B>если дедлок то что это за фуфло такое? на чистом винапи можно легко избежать этой проблемы, потому что там если эвент устанорвлен, то Wait не будет ждать.


То, что евент установлен, за это отвечает состояние этого объекта — некий bool, условно говоря.
С условной переменной функции ожидания проверяют предикат. Если там проверяется какой-то bool, то будет как с виндовым эвентом.
wait увидит true и не будет ждать. notify_one/notify_all только выполняют пробуждение ждущих потоков, чтобы те смогли проверить свои предикаты.

В условной переменной можно использовать и более сложные состояния, опрос которых при вызове предиката защищен мьютексом.
Например, аналог WaitForMultipleObjects можно сделать из условной переменной и массива bool.
Re: condition_variable.notify_all
От: Zhendos  
Дата: 20.07.20 11:43
Оценка: +1
Здравствуйте, Barbar1an, Вы писали:

B>я так и не понял из доков, если notify_all вызван до того как ожидатель вызвал wait , то что мы получим?

B>дедлок?

Дедлок это по определению другая ситуация.
А так да, до следующего вызова "notify" "wait" не вернет управление.
Но не понятно в чем проблема,
все же защищено mutex и поэтому атомарно.
То есть
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{return !queue.empty();});


    {
        std::lock_guard<std::mutex> lk(cv_m);
        queue.push(xyz);
    }
    cv.notify_all();


То есть если даже notify_all будет вызван до wait,
ничего плохого не произойдет и wait сразу вернет управление
ничего не блокируя благодаря предикату в wait
Re[2]: condition_variable.notify_all
От: Barbar1an Украина  
Дата: 20.07.20 11:56
Оценка:
Здравствуйте, qaz77, Вы писали:

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


B>>я так и не понял из доков, если notify_all вызван до того как ожидатель вызвал wait , то что мы получим?

B>>дедлок?

B>>если дедлок то что это за фуфло такое? на чистом винапи можно легко избежать этой проблемы, потому что там если эвент устанорвлен, то Wait не будет ждать.


Q>То, что евент установлен, за это отвечает состояние этого объекта — некий bool, условно говоря.

Q>С условной переменной функции ожидания проверяют предикат. Если там проверяется какой-то bool, то будет как с виндовым эвентом.
Q>wait увидит true и не будет ждать. notify_one/notify_all только выполняют пробуждение ждущих потоков, чтобы те смогли проверить свои предикаты.

Q>В условной переменной можно использовать и более сложные состояния, опрос которых при вызове предиката защищен мьютексом.

Q>Например, аналог WaitForMultipleObjects можно сделать из условной переменной и массива bool.

да , но меня напрягает что для тривиальной задачи "подождать пока чтото закончится", мне нужно иметь ТРИ переменые: мутекс, кондишн и чтото для предиката
хотя это можно сделать одним винапишным Event'ом
Я изъездил эту страну вдоль и поперек, общался с умнейшими людьми и я могу вам ручаться в том, что обработка данных является лишь причудой, мода на которую продержится не более года. (с) Эксперт, авторитет и профессионал из 1957 г.
Отредактировано 20.07.2020 11:57 Barbar1an . Предыдущая версия .
Re[3]: condition_variable.notify_all
От: watchmaker  
Дата: 20.07.20 12:13
Оценка:
Здравствуйте, Barbar1an, Вы писали:


B>да , но меня напрягает что для тривиальной задачи "подождать пока чтото закончится", мне нужно иметь ТРИ переменые: мутекс, кондишн и чтото для предиката

B>хотя это можно сделать одним винапишным Event'ом

Есть разные подходы к обработке событий, которые имеют разную производительность и подходят к различным ситуациям:

Условные переменные — это edge, а event в winapi — level. Неудивительно, что возникают неудобства, если использовать один подход для эмуляции другого
Собственно, если хочешь использовать только чистый с++, то возьми класс, который реализует примитив Event и скрывает всю работу с условными переменными внутри себя. А потом используй только этот класс.


B>хотя это можно сделать одним винапишным Event'ом

А что не std::future, кстати говоря?
Re[3]: condition_variable.notify_all
От: qaz77  
Дата: 20.07.20 12:21
Оценка: 6 (1) +2 -1
Здравствуйте, Barbar1an, Вы писали:

B>да , но меня напрягает что для тривиальной задачи "подождать пока чтото закончится", мне нужно иметь ТРИ переменые: мутекс, кондишн и чтото для предиката

B>хотя это можно сделать одним винапишным Event'ом

Просто более низкоуровневая абстракция, на базе которой можно сделать и семафор, и много чего еще.

Если хочется как виндовый эвент и одной переменной, сделай класс типа этого:
class thread_event
{
public:
    explicit thread_event(bool initial_state = false);

    bool get() const;
    void reset();
    void signal(); // wake up all waiting threads

    void wait();
    bool wait(int timeout_millisecs); // false - timeout expired

private:
    std::condition_variable m_cv;
    mutable std::mutex m_mtx;
    bool m_state;

    thread_event(const thread_event&) = delete;
    thread_event& operator=(const thread_event&) = delete;
};

thread_event::thread_event(bool initial_state /*= false*/):
    m_state(initial_state)
{
}

bool thread_event::get() const
{
    std::lock_guard<std::mutex> lock(m_mtx);
    return m_state;
}

void thread_event::reset()
{
    std::lock_guard<std::mutex> lock(m_mtx);
    m_state = false;
}

void thread_event::signal()
{
    std::lock_guard<std::mutex> lock(m_mtx);
    m_state = true;
    m_cv.notify_all();
}

void thread_event::wait()
{
    std::unique_lock<std::mutex> lock(m_mtx);
    m_cv.wait(lock, [this]() { return m_state; });
}

bool thread_event::wait(int timeout_millisecs)
{
    std::unique_lock<std::mutex> lock(m_mtx);
    return m_cv.wait_for(lock, std::chrono::milliseconds(timeout_millisecs), [this]() { return m_state; });
}
Re[4]: condition_variable.notify_all
От: Barbar1an Украина  
Дата: 20.07.20 12:21
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>А что не std::future, кстати говоря?


поток который я жду создается не мной
Я изъездил эту страну вдоль и поперек, общался с умнейшими людьми и я могу вам ручаться в том, что обработка данных является лишь причудой, мода на которую продержится не более года. (с) Эксперт, авторитет и профессионал из 1957 г.
Re[4]: condition_variable.notify_all
От: Alexander G Украина  
Дата: 20.07.20 14:31
Оценка: +1
Здравствуйте, qaz77, Вы писали:


Q>Просто более низкоуровневая абстракция, на базе которой можно сделать и семафор, и много чего еще.


Ну-у можно сделать condition_variable из семафора, а можно наоборот.
Наоборот, кстати, лучше выходит, оптимальнее, что говорит о том, что не очень низкоуровневая абстракция.
Русский военный корабль идёт ко дну!
Re[5]: condition_variable.notify_all
От: qaz77  
Дата: 20.07.20 14:47
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Ну-у можно сделать condition_variable из семафора, а можно наоборот.

AG>Наоборот, кстати, лучше выходит, оптимальнее, что говорит о том, что не очень низкоуровневая абстракция.

В контексте поддержки многопоточности в С++11 есть только мьютекс и условная переменная.
Если оставаться в рамках std::, то семафор придется из них собирать...

А так, согласен, что на разных платформах те или иные примитивы более родные и/или эффективные.
Re[2]: condition_variable.notify_all
От: B0FEE664  
Дата: 20.07.20 15:22
Оценка: +1
Здравствуйте, Zhendos, Вы писали:

Z>
Z>    std::unique_lock<std::mutex> lk(m);
Z>    cv.wait(lk, []{return !queue.empty();});
Z>


Z>
Z>    {
Z>        std::lock_guard<std::mutex> lk(cv_m);
Z>        queue.push(xyz);
Z>    }
Z>    cv.notify_all();
Z>


cv_m и m — это два разных mutex'а?
И каждый день — без права на ошибку...
Re[4]: condition_variable.notify_all
От: B0FEE664  
Дата: 20.07.20 15:50
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Если хочется как виндовый эвент и одной переменной, сделай класс типа этого:

Вот конкретно этот thread_event содержит потенциальную ошибку:
thread 1                                    |  thread 2
                                            | 
if ( thread_event::get() )                  | 
                                            |   thread_event::signal();
  thread_event::reset();                    |
thread_event::wait();  <--- signal потерен  |


Методы thread_event::get() и thread_event::reset() следует выкинуть, как непригодные к использованию.
  Скрытый текст
Q>
Q>class thread_event
Q>{
Q>public:
Q>    explicit thread_event(bool initial_state = false);

Q>    bool get() const;
Q>    void reset();
Q>    void signal(); // wake up all waiting threads

Q>    void wait();
Q>    bool wait(int timeout_millisecs); // false - timeout expired

Q>private:
Q>    std::condition_variable m_cv;
Q>    mutable std::mutex m_mtx;
Q>    bool m_state;

Q>    thread_event(const thread_event&) = delete;
Q>    thread_event& operator=(const thread_event&) = delete;
Q>};

Q>thread_event::thread_event(bool initial_state /*= false*/):
Q>    m_state(initial_state)
Q>{
Q>}

Q>bool thread_event::get() const
Q>{
Q>    std::lock_guard<std::mutex> lock(m_mtx);
Q>    return m_state;
Q>}

Q>void thread_event::reset()
Q>{
Q>    std::lock_guard<std::mutex> lock(m_mtx);
Q>    m_state = false;
Q>}

Q>void thread_event::signal()
Q>{
Q>    std::lock_guard<std::mutex> lock(m_mtx);
Q>    m_state = true;
Q>    m_cv.notify_all();
Q>}

Q>void thread_event::wait()
Q>{
Q>    std::unique_lock<std::mutex> lock(m_mtx);
Q>    m_cv.wait(lock, [this]() { return m_state; });
Q>}

Q>bool thread_event::wait(int timeout_millisecs)
Q>{
Q>    std::unique_lock<std::mutex> lock(m_mtx);
Q>    return m_cv.wait_for(lock, std::chrono::milliseconds(timeout_millisecs), [this]() { return m_state; });
Q>}
Q>
И каждый день — без права на ошибку...
Re[5]: condition_variable.notify_all
От: qaz77  
Дата: 20.07.20 16:21
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Вот конкретно этот thread_event содержит потенциальную ошибку:

BFE>
BFE>thread 1                                    |  thread 2
BFE>                                            | 
BFE>if ( thread_event::get() )                  | 
BFE>                                            |   thread_event::signal();
BFE>  thread_event::reset();                    |
BFE>thread_event::wait();  <--- signal потерен  |
BFE>


BFE>Методы thread_event::get() и thread_event::reset() следует выкинуть, как непригодные к использованию.

BFE>

Здесь get скорее диагностический метод, типа WaitForSingleObject(hEvent, 0).
А reset — это ручной сброс, аналог ResetEvent из Win API.

О какой-то потере здесь говорить бессмысленно, все зависит от контекста использования.
Люди привыкшие писать под винду на эвентах знают, как все это приготовить.
Re[6]: condition_variable.notify_all
От: Alexander G Украина  
Дата: 20.07.20 16:31
Оценка: 5 (1)
Здравствуйте, qaz77, Вы писали:


Q>В контексте поддержки многопоточности в С++11 есть только мьютекс и условная переменная.

Q>Если оставаться в рамках std::, то семафор придется из них собирать...

Это да. Хотя контексте C++20 дали и семафор, и барьер, и atomic<T>::wait, дождаться теперь его массовости.

Q>А так, согласен, что на разных платформах те или иные примитивы более родные и/или эффективные.


Даже от версии.

В Windows XP, то мьютекс на событие/семафор -- это самое "родное", за неимением ничего другого (ну кроме рекрусивного юзерспейс мьютекса CRITICAL_SECTION).

С Vista есть уже SRWLOCK / CONDITION_VARIABLE, и из-за отсуствия проблем с инициализацией, нехваткой ресурсов, и вообще эффективным user-ожиданием, уже есть причины предпочесть их, несмотря на то, что показанный выше семафор на них сложноват и совершает лишние операции. Т.е. на Windows Event как бы можно сэкономить на спичках, но при этом придётся самомоу городить атомарную юзерспейс часть.

Наконец, WaitOnAddress с Windows 8 имеет и плюсы с эффективной иницииализацией, и большую свободу действий, так, что семафор, или что там на нём строить решили, не будет делать ничего лишнего.
Русский военный корабль идёт ко дну!
Re[6]: condition_variable.notify_all
От: B0FEE664  
Дата: 20.07.20 17:12
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Здесь get скорее диагностический метод, типа WaitForSingleObject(hEvent, 0).

Такая диагностика скорее введёт в заблуждение, чем поможет.

Q>А reset — это ручной сброс, аналог ResetEvent из Win API.

А кроме "ручного сброса" другого и нет в коде.

Q>О какой-то потере здесь говорить бессмысленно, все зависит от контекста использования.

В большинстве случаев эту ошибку не заменят, просто в редких случаях (очень редких) программа будет подвисать на какое-то (timeout) время или до второго события...

Q>Люди привыкшие писать под винду на эвентах знают, как все это приготовить.

Если я правильно помню, на виндах WaitFor... сбрасывает эвент в несигнальное состояние. В подавляющем большинстве случаев это важно для корректной работы приложения.
И каждый день — без права на ошибку...
Re[7]: condition_variable.notify_all
От: Alexander G Украина  
Дата: 20.07.20 17:21
Оценка:
Здравствуйте, B0FEE664, Вы писали:

Q>>Люди привыкшие писать под винду на эвентах знают, как все это приготовить.

BFE>Если я правильно помню, на виндах WaitFor... сбрасывает эвент в несигнальное состояние. В подавляющем большинстве случаев это важно для корректной работы приложения.

Есть auto-reset event, который сбрасывается сам, он работает как бинарный семафор,
А есть manual-reset, который не сбрасывается сам ("бесконечноарный семафор" )

Здесь manual-reset.
Русский военный корабль идёт ко дну!
Re[7]: condition_variable.notify_all
От: qaz77  
Дата: 21.07.20 06:15
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Если я правильно помню, на виндах WaitFor... сбрасывает эвент в несигнальное состояние. В подавляющем большинстве случаев это важно для корректной работы приложения.


Там есть два вида событий — с ручным сбросом и с автоматическим.
При ручном сбросе wait не меняет состояния, т.е. при вызове signal проснутся все ожидающие потоки.
Если какие-то потоки не дошли еще до wait в момент вызова signal, то такие потоки увидят то состояние, которое будет при входе в wait
(это та ситуация, которая вызывала сомнения в исходном вопросе).

Обычно reset и signal вызывает один поток, который выполняет какую-то работу, а wait — другие потоки, которым нужен результат этой работы.

Например, такая иллюстрация.
Индексирующий какой-то текст поток управляет событием, а потоки выполняющие поиск ждут готовности индекса.
index thread                 | search thread 1            | search thread 2            
-----------------------------|----------------------------|--------------------------------
ev.reset() // no valid index |                            |   
                             | ev.wait() // not ready yet | 
// build full text index...  |                            |
                             |                            |
ev.signal() // ready         | // wake up                 |
                             |                            | ev.wait() // pass through


Тут событие используется как флаг валидности индекса.
В общем, событие с ручным сбросом — это почти как std::atomic<bool>, но с возможностью ожидания.
Re: condition_variable.notify_all
От: a7d3  
Дата: 21.07.20 09:04
Оценка:
Здравствуйте, Barbar1an, Вы писали:

B>я так и не понял из доков, если notify_all вызван до того как ожидатель вызвал wait , то что мы получим?

B>дедлок?

B>если дедлок то что это за фуфло такое? на чистом винапи можно легко избежать этой проблемы, потому что там если эвент устанорвлен, то Wait не будет ждать.


Многих сбивает с толку, что в С++ condition variables являются всего лишь реализацией давно известного «монитора».
Re[7]: condition_variable.notify_all
От: qaz77  
Дата: 21.07.20 09:49
Оценка: +1
Здравствуйте, B0FEE664, Вы писали:

Q>>Здесь get скорее диагностический метод, типа WaitForSingleObject(hEvent, 0).

BFE>Такая диагностика скорее введёт в заблуждение, чем поможет.

Не согласен.

Могут быть такие места в программе, где точно известно о требуемом состоянии event'ов.
Почему бы не поставить там assert?
Я имею в виду поток, который рулит событиями, т.е. никакой гонки нет в принципе.

Например, есть три события, состояния которых связаны инвариантом.
Поток, который ими рулит, меняет состояния событий (reset/signal) в критической секции для атомарности с т.з. других потоков.
Как проверить инвариант внутри критической секции?
Например, так:
assert(ev1.get() && ev2.get() && !ev3.get());


Вот такой еще пример.
В posix для семафора есть функция sem_getvalue.
Там в любой момент может значение семафора изменится, но тем не менее функция такая есть.
Re: condition_variable.notify_all
От: Pzz Россия https://github.com/alexpevzner
Дата: 21.07.20 21:36
Оценка:
Здравствуйте, Barbar1an, Вы писали:

B>я так и не понял из доков, если notify_all вызван до того как ожидатель вызвал wait , то что мы получим?

B>дедлок?

Получим то, что ожидатель не будет нотифицирован. А дедлок это или не дедлок, зависит от того, как условная переменная используется.

B>если дедлок то что это за фуфло такое? на чистом винапи можно легко избежать этой проблемы, потому что там если эвент устанорвлен, то Wait не будет ждать.


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