У вас может быть столько замков, сколько вы хотите, но вы получите условия гонки
если только 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
в интерфейсе. Как только провайдер
передал объект в очередь, он больше не имеет права доступа
она.)