Каковы общие применения переменных условий в С++?

Я пытаюсь узнать переменные условия. Я хотел бы знать, каковы обычные ситуации, когда используются переменные условия.

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

Каковы другие проектные ситуации, когда вам нужна переменная условия, которая будет использоваться?

Я бы предпочел примеры, основанные на опыте, например, в реальных реальных приложениях.

Ответы

Ответ 1

Одно использование переменных условий, которые немного сложнее, чем просто очередь сообщений, заключается в "совместном использовании блокировки", где разные потоки ожидают тонко отличающихся условий одного и того же основного характера. Например, у вас есть (очень шонкий, упрощенный) веб-кеш. Каждая запись в кеше имеет три возможных состояния: нет, 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 сам по себе не имеет определенных кодов ошибок, поэтому его реализация с возможностью сбоя не оставляет вашего вызывающего абонента с любыми хорошими вариантами...

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

Ответ 2

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

Ответ 3

Я использовал его для отправки синхронизированных сообщений, где был добавлен объект синхронизации.
Объект sync состоял из переменной условия с "готовым" булевым.
В функции syncMsg:: send() была функция sync- > wait(), а в функции syncMsg:: handle() была синхронизация → go().

Должно использоваться разумно из-за возможных взаимоблокировок.

Ответ 4

Я использую переменные условия вместо подверженных ошибкам объектов событий Win32. С condvars вам не нужно так беспокоиться о ложной сигнализации. Кроме того, легче ждать появления нескольких событий.

Кондвары также могут заменить семафоры, потому что они более универсальны.

Ответ 5

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

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

Фоновый поток следует этой базовой логике

void threadFunction() {
    initialisation();

    while(! shutdown()) {
        backgroundTask();

        shutdown_condition_wait(timeout_value);
    }

    cleanup();
}

Это позволяет отключить фоновый поток быстро и изящно.

Если у меня есть ряд таких потоков, основная функция сигнализирует каждому об отключении, а затем присоединяется к каждому из них после следующего. Это позволяет параллельному отключению каждого компонента нити.