действительно ли boost :: wait и boost :: condition должны использовать один и тот же объект мьютекса - PullRequest
5 голосов
/ 03 августа 2011
boost::condition_variable cond;
boost::mutex mutex;

//thread #1
for(;;)
{
    D * d = nullptr;

    while( cb.pop(d) )  //cb is a circular buffer and manage is own mutex/lock internally
    {
        //...do something with d
    }
    boost::unique_lock<boost::_mutex> lock( mutex );
    cond.wait( mutex );
}

//thread #2
while(1)
{
    getchar();

    for( int i = 0 ; i < 1000 ; ++i )
    {
        cb.push(new D(i));      //one producer so no lock required

        cond.notify_one();     // no lock required here ?
    }
}

Мне интересно, нормально ли, если мой контейнер данных имеет свою собственную блокировку, чтобы избежать гонки данных, и с другой стороны, boost :: wait использует механизм блокировки / мьютекса, как это указано в документации boost?*

В противном случае thread1 является потребителем, в случае, если у меня есть только один поток, который "потребляет", кажется, что блокировка, требуемая ожиданием, немного избыточна, не так ли? РЕДАКТИРОВАТЬ : я не забочусь о пропущенном обновлении. При получении обновления я обновляю объект полученными данными.Я просто хочу более свежее обновление, а не все обновления

Ответы [ 4 ]

2 голосов
/ 03 августа 2011

У вас может быть столько замков, сколько вы хотите, но вы получите условия гонки если только pop и push не защищены тем же мьютексом, что и wait и notify (и замок не освобождается между решение ждать и собственно ждать). Стандартная идиома:

//  thread #1
// ...
{
    boost::unique_lock<boost::mutex> lock( mutex );
    while ( !cb.pop( d ) )
        cond.wait( mutex );
}
// process d


//  thread #2
// ...
{
    boost::unique_lock<boost::mutex> lock( mutex );
    cb.push( new D(i) );
    cond.notify_one();
}

Попытка зацикливаться на pop в потоке # 1 более сложна, по крайней мере если вы хотите снять блокировку во время обработки d. Вам нужно что-то вроде:

boost::unique_lock<boost::mutex> lock( mutex );
while ( cb.pop( d ) ) {
    lock.unlock();
    //  process d
    lock.lock();
}
cond.wait( mutex );

Все сложнее, и я не вижу, что вы от этого выиграете. Просто используйте обычный шаблон, который, как известно, работает надежно.

FWIW: ваш код полон условий гонки: для начала: pop происходит сбой в потоке 1, есть переключение контекста, поток 2 делает push и notify, затем возвращаемся к потоку 1, который выполняет cond.wait. И ждет, несмотря на то, что в очереди что-то есть.

Могу добавить, что для таких типов, как кольцевые буферы для управления собственной блокировкой мьютекса. Зернистость слишком низко. Исключение составляют случаи, когда инструкция pop на самом деле ожидает что-то есть, то есть (на основании std::deque):

T* CircularBuffer::push( std::auto_ptr<T> in )
{
    boost::unique_lock<boost::mutex> l( myMutex );
    myQueue.push_back( in.get() );
    in.release();  // Only after the push_back has succeeded!
    myCondition.notify_all();
}

std::auto_ptr<T> CircularBuffer::pop()
{
    boost::unique_lock<boost::mutex> l( myMutex );
    while ( myQueue.empty() ) {
        myCondition.wait();
    }
    std::auto_ptr<T> result( myQueue.front() );
    myQueue.pop_front();
    return result;
}

(Обратите внимание на использование auto_ptr в интерфейсе. Как только провайдер передал объект в очередь, он больше не имеет права доступа она.)

1 голос
/ 03 августа 2011

Кондвар должен использовать мьютекс, который защищает данные (ну, не совсем, точнее ниже), иначе вы пропустите обновления:

producer             consumer

                     while(cb.pop()) ...;
cb.push();
cond.notify_one();
                     cond.wait(); // OOPS. I missed the notification!

Чтобы избежать этого, необходимо у потребителя:

  1. Замок mutex
  2. Убедитесь, что условие не выполняется
  3. cond.wait(mutex);
  4. Вернуться к проверке состояния (mutex снова заблокирован)

и в производителе вы должны:

  1. Блокировка mutex
  2. Сделайте условие истинным (т.е. cb.push ())
  3. cond.notify_one()
  4. Теперь вы можете наконец разблокировать mutex.

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

Кстати, на самом деле можно создать механизм уведомления, который не должен взаимодействовать с блокировкой. Требуются отдельные операции для «подписать меня на сигнал» и «ждать сигнала», где последний сразу возвращается, когда сигнал появился с момента первого (и вы проверяете условие между ними). Однако я не видел такого механизма ни в одной переносимой библиотеке потоков.

Редактировать: С другой стороны, семафоры могут быть более подходящими для управления очередью сообщений. Имейте семафор, который отражает количество элементов в очереди. Вы up это для каждого push и down это перед каждым pop (или просто встраиваете его в саму очередь, поэтому pop просто будет ждать появления чего-либо, если очередь пуста).

0 голосов
/ 03 августа 2011

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

В вашем случае у вас уже есть потокобезопасный контейнер. Не лучше ли поместить переменную condtion в ваш контейнер и позволить ей использовать свой мьютекс?

0 голосов
/ 03 августа 2011

Если функции push и pop вашего кольцевого буфера являются поточно-ориентированными, тогда вам не требуется дополнительная синхронизация.

Если у вас было несколько считывателей, вы можете использовать устройства чтения / записиблокировка для одновременного чтения нескольких потоков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...