Почему функции условной переменной pthread требуют взаимного исключения?

Я читаю на pthread.h; связанные с условной переменной функции (как pthread_cond_wait(3)) потребуйте взаимного исключения как аргумента. Почему? Насколько я могу сказать, я собираюсь быть созданием взаимного исключения только для использования в качестве того аргумента? Что то взаимное исключение, как предполагается, делает?

177
задан ELLIOTTCABLE 4 May 2010 в 08:05
поделиться

3 ответа

Это просто способ реализации (или изначально) переменных условий.

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

Ожидание "атомарно" разблокирует мьютекс, позволяя другим получить доступ к переменной условия (для сигнализации). Затем, когда переменная условия передается или транслируется, один или несколько потоков в списке ожидания будут разбужены, и мьютекс снова будет волшебным образом заблокирован для этого потока.

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

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            do the work.
    unlock mutex.
    clean up.
    exit thread.

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

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

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            copy work to thread local storage.
            unlock mutex.
            do the work.
            lock mutex.
    unlock mutex.
    clean up.
    exit thread.

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

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

lock mutex.
flag work as available.
signal condition variable.
unlock mutex.

Подавляющее большинство того, что часто ошибочно называют ложным пробуждением, обычно всегда происходило из-за того, что в их вызове (широковещательном) pthread_cond_wait был подан сигнал о нескольких потоках, можно было бы вернуться с мьютексом, выполнить работу, а затем снова ждать.

Тогда вторая сигнальная нить могла выйти, когда не было никакой работы. Таким образом, у вас должна была быть дополнительная переменная, указывающая, что работа должна быть выполнена (здесь она изначально была защищена мьютексом с помощью пары condvar / mutex - однако другие потоки должны были заблокировать мьютекс перед его изменением).

Было технически возможно, чтобы поток вернулся из состояния ожидания, не будучи запущенным другим процессом (это настоящее ложное пробуждение), но за все мои годы работы над pthreads, оба в разработке / service кода, и, как пользователь, я ни разу не получил ни одного из них. Возможно, это было просто потому, что у HP была достойная реализация: -)

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

187
ответ дан 23 November 2019 в 20:19
поделиться

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

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

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

Представьте, что поток-производитель передает некоторые данные другому потоку-потребителю через указатель some_data .

while(1) {
    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    char *data = some_data;
    some_data = NULL;
    handle(data);
}

вы, естественно, получите много условий гонки, что, если бы другой поток выполнил some_data = new_data сразу после того, как вы проснулись, но до того, как вы сделали data = some_data

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

while(1) {

    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    pthread_mutex_lock(&mutex);
    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

Не будет работать, все еще есть вероятность состояния гонки между пробуждением и захватом мьютекса. Размещение мьютекса перед pthread_cond_wait вам не поможет, так как теперь вы будете удерживать мьютекс во время ожидания - т.е. производитель никогда не сможет захватить мьютекс. (обратите внимание, в этом случае вы можете создать вторую условную переменную, чтобы сигнализировать производителю, что вы закончили с some_data - хотя это станет сложным, особенно если вам нужно много производителей / потребителей.)

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

while(1) {
    pthread_mutex_lock(&mutex);
    while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also 
                               // make it robust if there were several consumers
       pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
    }

    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

(производитель, естественно, должен будет принять те же меры предосторожности, всегда защищая some_data одним и тем же мьютексом и следя за тем, чтобы он не перезаписывал some_data, если some_data в настоящее время! = NULL)

57
ответ дан 23 November 2019 в 20:19
поделиться

Предполагается, что мьютекс заблокирован, когда вы вызываете pthread_cond_wait ; когда вы вызываете его атомарно, оба разблокируют мьютекс, а затем блокируют условие. Как только условие получено, оно снова атомарно блокирует его и возвращается.

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

3
ответ дан 23 November 2019 в 20:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: