Проблема с boost::condtiion::timed_wait()
От: syomin  
Дата: 20.09.06 08:52
Оценка:
День добрый!
При портировании одной библиотеки из UNIX в Windows столкнулся с тем, что некоторый кусок кода корректно работает в UNIX и не работает в Windows. Подозрение пало на функцию boost::condition::timed_wait()... Чтобы не засорять форум, я написал маленький тестик:
#include <stdexcept>
#include <iostream>

#include <boost/thread.hpp>

#include <windows.h>

using namespace boost;
using namespace std;

class AsyncQueue {
public:
    AsyncQueue() : m_counter(0) {}

    void push()
    {
        mutex::scoped_lock lock(m_mutex);
        m_counter++;
        m_condition.notify_one();
    }

    void timed_pop(const xtime &time)
    {
        mutex::scoped_lock lock(m_mutex);

        if(m_counter) {
            m_counter--;
            return;
        }

        if(!m_condition.timed_wait(lock, time)) return;

        if(!m_counter) throw out_of_range("oops...");

        m_counter--;
    }

private:
    mutex m_mutex;
    condition m_condition;

    int m_counter;
};

static AsyncQueue queue;

static void start()
{
    for(;;) {
        queue.push();
        Sleep(100);
    }
}

int main(int argc, char **argv)
{
    thread thread(start);

    xtime time;

    for(;;) {
        xtime_get(&time, TIME_UTC);
        time.nsec += 10000;
        queue.timed_pop(time);
    }

    return 0;
}

Класс AsyncQueue — простенький контейнер для которого определены 2 операции — push() и timed_pop(). Фактически в контейнере ничего не хранится (это же тест как-никак , а просто ведется счетчик количества элементов (m_counter). Для проверки всего этого дела создается дополнительный поток и с него выполняется операция push(). Основной поток имитирует получение данных из очереди.

Внимание вопрос: почему иногда генерируется исключение out_of_range() при вызове timed_pop()? Получается, что boost::condition::timed_wait() иногда возвращает true даже тогда, когда не было вызова wait_one()? Или я туплю?...

Добавлена раскраска кода — Кодт
Re: Проблема с boost::condtiion::timed_wait()
От: syomin  
Дата: 20.09.06 08:54
Оценка:
Совсем забыл про окружение:
Visual Studio 2005, Windows XP SP2 под управлением VMWare 5.5.1. Boost версии 1.33.1.
Re: Проблема с boost::condtiion::timed_wait()
От: 0rc Украина  
Дата: 20.09.06 10:50
Оценка:
Здравствуйте, syomin, Вы писали:
Два замечания:
1. Используйте теги форматирования кода.
2. Происходит переполнение m_counter
Re: Проблема с boost::condtiion::timed_wait()
От: Elifant  
Дата: 21.09.06 02:52
Оценка: 46 (3)
Здравствуйте, syomin, Вы писали:

S>День добрый!

S>При портировании одной библиотеки из UNIX в Windows столкнулся с тем, что некоторый кусок кода корректно работает в UNIX и не работает в Windows. Подозрение пало на функцию boost::condition::timed_wait()... Чтобы не засорять форум, я написал маленький тестик:

S> void timed_pop(const xtime &time)

S> {
S> mutex::scoped_lock lock(m_mutex);

S> if(m_counter) {

S> m_counter--;
S> return;
S> }

S> if(!m_condition.timed_wait(lock, time)) return;


S> if(!m_counter) throw out_of_range("oops...");


S> m_counter--;

S> }

S>Внимание вопрос: почему иногда генерируется исключение out_of_range() при вызове timed_pop()? Получается, что boost::condition::timed_wait() иногда возвращает true даже тогда, когда не было вызова wait_one()? Или я туплю?...


s/wait_one/notify_one/

Именно так. Существуют так называемые spurious wakeups, т.е. когда функция wait возвращается, но условие ожидания не выполнено.
Из-за этого конструкция
if (! m_counter) m_condition.wait(lock);
неверна, следует использовать
while (! m_counter) m_condition.wait(lock);
или вариант с дополнительным аргументом
m_condition.wait(lock, boost::lambda::var(m_counter));
Re[2]: Занятно
От: remark Россия http://www.1024cores.net/
Дата: 21.09.06 13:59
Оценка:
Здравствуйте, Elifant, Вы писали:

E>Именно так. Существуют так называемые spurious wakeups, т.е. когда функция wait возвращается, но условие ожидания не выполнено.

E>Из-за этого конструкция
if (! m_counter) m_condition.wait(lock);
неверна, следует использовать
while (! m_counter) m_condition.wait(lock);
или вариант с дополнительным аргументом
m_condition.wait(lock, boost::lambda::var(m_counter));


Занятно, занятно...
здесь одна из первых ссылок googla по запросу spurious wakeups, она немного проясняет причины такого поведения.

Хотя совершенно непонятно, как это происходит на Win32? Как они вообще добились такого поведения, что происходят spurious wakeups? Ведь в Win32 такого нет, т.е. если уж мы закончили ждать на объекте, значит его кто-то перевёл в свободное состояние (или я совсем от жизни отстал).


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: UP
От: remark Россия http://www.1024cores.net/
Дата: 26.09.06 15:33
Оценка:
Здравствуйте, remark, Вы писали:

Кто-нить может чо-нить добавить?
Под Win32 такое может быть???
Мне кажется, под Win32 такого быть не может, т.ч. ошибка не в этом.

R>


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Занятно
От: Кодт Россия  
Дата: 26.09.06 17:29
Оценка: 12 (1)
Здравствуйте, remark, Вы писали:

R>Хотя совершенно непонятно, как это происходит на Win32? Как они вообще добились такого поведения, что происходят spurious wakeups? Ведь в Win32 такого нет, т.е. если уж мы закончили ждать на объекте, значит его кто-то перевёл в свободное состояние (или я совсем от жизни отстал).


В Win32 нет собственно condition'а, он эмулируется затейливой связкой из нескольких мьютексов и, кажется, эвента.

Когда возникает гонка между пробуждением от ожидания и извещением — то, в принципе, возможны две ситуации:
— извещение теряется — тогда следующего пробуждения не будет, и поток повиснет
— закладывается лишнее извещение — тогда следующее пробуждение будет ложным
Поскольку ложные пробуждения "входят в стоимость тура", второй сценарий считается безопасным.

Ну и кроме того, пробуждение может быть ложным из-за того, что после отправки извещения условие инвалидировалось до пробуждения. В простых ситуациях, как у тебя, такое помыслить невозможно; но в общем случае запросто.
Поэтому ожидание в цикле — единственно верный подход.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.