Для того, чтобы ожидать некоего события на условной переменной (Condition Variable) надо захватывать сопутствующую ей мьютекс-блокировку. На время ожидания поток освобождает ее, но как только внешний поток пошлет ей сигнал, она снова захватит ее. Сделано это для того, чтобы поток-производитель и поток-потребитель не конфликтовали за общий ресурс (например, очередь, в которую производитель кладет сообщение):
Код потребителя
void* squeue_pop(squeue_t* sq) {
/* Объявления */
pthread_mutex_lock(&sq->sq_mutex);
retry:
if(sq->sq_head == NULL) {
pthread_cond_wait(&sq->sq_cv, &sq->sq_mutex);
if(!sq->sq_is_destroyed)
goto retry;
else
return NULL;
}
/* Извлекаем элемент */
pthread_mutex_unlock(&sq->sq_mutex);
/* Возвращаем объект */
}
Код производителя:
void squeue_push(squeue_t* sq, void* object) {
/* Объявления */
pthread_mutex_lock(&sq->sq_mutex);
/* Помещаем элемент в очередь */
pthread_cond_signal(&sq->sq_cv);
pthread_mutex_unlock(&sq->sq_mutex);
}
В идеале данный код должен работать следующим образом:
ПОТРЕБИТЕЛЬ | ПРОИЗВОДИТЕЛЬ
pthread_mutex_lock |
|
pthread_cond_wait |
pthread_mutex_unlock |
[WAITING] |
| ...
| pthread_mutex_lock
| [QUEUE msg]
| pthread_cond_signal
| pthread_mutex_unlock
|
pthread_mutex_lock |
|
[PROCESS msg] |
А вот всегда ли нужно захватывать блокировку на стороне производителя? man-страница на pthread_cond_signal с этим не очень-то помогает:
The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not
it currently owns the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have
associated with the condition variable during their waits; however, if predictable scheduling behavior
is required, then that mutex shall be locked by the thread calling pthread_cond_broadcast() or
pthread_cond_signal().
Таким образом, мьютекс может удерживаться, а может и нет, а для предсказуемого поведения планировщика рекомендуется все же захватывать его. Некоторую подсказку на этот счет дает stackoverflow: http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex
Итак, все зависит от того, что является условием срабатывания в потоке-потребителе. Если мы не используем никаких внешних условий, но захватывать мьютекс надо обязательно, иначе мы можем получить ситуацию, при которой его срабатывание теряется. В таком случае сигнал может прийти раньше чем мы начнем ожидание на условной переменной:
ПОТРЕБИТЕЛЬ | ПРОИЗВОДИТЕЛЬ
pthread_mutex_lock |
| pthread_cond_signal
pthread_cond_wait |
pthread_mutex_unlock |
[WAITING] |
|
Однако если же мы используем внешнее условие - например проверяя голову очереди на равенство NULL, то удерживание мьютекса на время вызова pthread_cond_signal приведет к излишним переключениям контекста:
ПОТРЕБИТЕЛЬ | ПРОИЗВОДИТЕЛЬ ПРОИЗВ | ПОТР МЬЮТЕКС
pthread_mutex_lock | | ПОТР
| | ПОТР
pthread_cond_wait | | ПОТР
pthread_mutex_unlock | | ПОТР
[WAITING] | +-------+ -
| ... | -
| pthread_mutex_lock | ПРОИЗВ
| [QUEUE msg] | ПРОИЗВ
| pthread_cond_signal +-------+ ПРОИЗВ
| | ПРОИЗВ
pthread_mutex_lock | +-------+ ПРОИЗВ
| | ПРОИЗВ
| pthread_mutex_unlock +-------+ ПРОИЗВ
| | ПОТР
[PROCESS msg] | | ПОТР
Дело в том, что pthread_cond_signal, дергающий futex стремится сразу же отдать управление потоку-потребителю, а тот - немедленно захватить мьютекс, который уже удерживается потоком-производителем. После чего операционная система снова переключает контекст, и только когда поток-производитель освободит мьютекс, поток-потребитель может обрабатывать сообщение.
Решением этой проблемы является уже упомянутое "внешнее условие" и освобождение мьютекса до вызова pthread_cond_signal:
ПОТРЕБИТЕЛЬ | ПРОИЗВОДИТЕЛЬ ПРОИЗВ | ПОТР МЬЮТЕКС
pthread_mutex_lock | | ПОТР
| | ПОТР
pthread_cond_wait | | ПОТР
pthread_mutex_unlock | | ПОТР
[WAITING] | +-------+ -
| ... | -
| pthread_mutex_lock | ПРОИЗВ
| [QUEUE msg] | ПРОИЗВ
| pthread_mutex_unlock | ПРОИЗВ
| ... | -
| pthread_cond_signal +-------+ -
pthread_mutex_lock | | ПОТР
| | ПОТР
[PROCESS msg] | | ПОТР