Я пытаюсь узнать об условных переменных. Я хотел бы знать то, что является общими ситуациями, где условные переменные используются.
Один пример находится в блокирующейся очереди, где два потока получают доступ к очереди - поток производителя продвигает объект в очередь, в то время как потребительский поток выталкивает объект от очереди. Если очередь пуста, потребительский поток ожидает, пока сигнал не отправляется потоком производителя.
Каковы другие ситуации с дизайном, где Вам нужна условная переменная, которая будет использоваться?
Я предпочел бы примеры, базирующиеся на основе опыта, хотя, такие как примеры в реальных работающих приложениях.
Одно из вариантов использования условных переменных, которое немного сложнее, чем просто очередь сообщений, - это " совместно использовать блокировку ", где разные потоки ждут слегка разных условий одной и той же базовой природы. Например, у вас есть (очень громоздкий, упрощенный) веб-кеш. Каждая запись в кэше имеет три возможных состояния: отсутствует, IN_PROGRESS, COMPLETE.
getURL:
lock the cache
three cases for the key:
not present:
add it (IN_PROGRESS)
release the lock
fetch the URL
take the lock
update to COMPLETE and store the data
broadcast the condition variable
goto COMPLETE
COMPLETE:
release the lock and return the data
IN_PROGRESS:
while (still IN_PROGRESS):
wait on the condition variable
goto COMPLETE
Я на практике использовал этот шаблон для реализации варианта функции POSIX pthread_once
без какой-либо помощи планировщика. Причина, по которой я не мог использовать семафор или блокировку для once_control
, а просто выполнить инициализацию под блокировкой, заключается в том, что функция не могла завершиться с ошибкой, а Once_control
имел только тривиальная инициализация. В этом отношении pthread_once
сам по себе не имеет определенных кодов ошибок, поэтому его реализация для возможного сбоя не оставляет для вызывающего абонента никаких хороших вариантов ...
Конечно, с этим шаблоном вы должны быть осторожны насчет масштабирования. Каждый раз, когда любая инициализация завершается, каждый ожидающий поток просыпается, чтобы захватить блокировку. Поэтому, когда вы проектируете систему, вы очень тщательно думаете о сегментировании, а затем решаете, что вам не стоит беспокоиться о том, чтобы делать что-либо для ее реальной реализации, пока вы не увидите доказанные проблемы с производительностью.
Я использовал его для отправки синхронизированных сообщений, где был добавлен sync-объект.
Объект синхронизации состоял из переменной условия с булевым значением "готов".
В функции syncMsg::send() была sync->wait(), а в функции syncMsg::handle() была sync->go().
Следует использовать с осторожностью из-за возможных тупиков.
Я знаю, что это не очень полезно, но я использую переменную условия каждый раз, когда хочу, чтобы поток ждал, пока что-то произойдет, или ждал только до тех пор, пока что-то не произойдет.
Очень распространенная схема, в которой я использую переменную условия, - это фоновый поток, который просыпается каждые несколько минут для выполнения некоторой обработки, а затем снова засыпает. При выключении основной поток сигнализирует фоновому потоку о завершении работы, а затем присоединяется к нему. Фоновый поток ожидает выполнения условия с таймаутом, чтобы выполнить свой сон.
Фоновый поток следует этой базовой логике
void threadFunction() {
initialisation();
while(! shutdown()) {
backgroundTask();
shutdown_condition_wait(timeout_value);
}
cleanup();
}
Это позволяет фоновому потоку быстро и изящно завершить работу.
Если у меня есть несколько таких потоков, главная функция подает сигнал каждому из них на выключение, а затем присоединяется к каждому из них после следующего. Это позволяет каждому компоненту потока завершать работу параллельно.
Я использую условные переменные вместо подверженных ошибкам объектов Win32 Event. С кондварами вам не нужно так беспокоиться о ложных сигналах. Также легче дождаться возникновения нескольких событий.
Кондвары также могут заменять семафоры, поскольку они более универсальны.
Примером, в дополнение к модели потребитель-производитель, которую вы уже упомянули, является использование в барьерной синхронизации. Когда потоки входят в барьер, если есть еще другие потоки, которым нужно войти в барьер, то они ожидают переменную условия. Последний поток, вошедший в барьер, сигнализирует об условии.