Подтвердить что ты не робот

Делает pthread_cond_wait (& cond_t, & mutex); разблокировать, а затем заблокировать мьютексы?

Я использую pthread_cond_wait(&cond_t, &mutex); в своей программе, и я задаюсь вопросом, почему эта функция нуждается в качестве второго параметра для переменной mutex.

Разблокирует ли pthread_cond_wait() мьютекс в начале (начало выполнения pthread_cond_wait()) и затем блокируется, когда он заканчивается (как раз перед тем, как оставить pthread_cond_wait())?

4b9b3361

Ответ 1

Когда первый поток вызывает pthread_cond_wait(&cond_t, &mutex);, он освобождает мьютекс и ожидает, пока условие cond_t будет объявлено как завершенное и mutex.

Поэтому, когда pthread_cond_signal вызывается в другом потоке, он не "пробуждает" поток, который все еще ждет. mutex должен быть разблокирован первым, только тогда есть вероятность, что первый поток получит блокировку, что означает, что "при успешном возврате pthread_cond_wait мьютекс должен быть заблокирован и должен принадлежать вызывающему потоку."

Ответ 2

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

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

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

  • Цель мьютекса не для защиты переменной условия; это защита предиката, в котором переменная условия используется как механизм сигнализации. Это ручная работа, наиболее часто неправильно понимаемая идиома переменных состояния pthread и их мьютексов. Переменной условия не требуется защита взаимного исключения; данные предиката. Подумайте о предикате как внешнем состоянии, которое контролируется пользователями пары condition-variable/mutex.

Например, тривиальный, но явно неправильный фрагмент кода для ожидания булевого флага fSet:

bool fSet = false;

int WaitForTrue()
{
    while (!fSet)
    {
        sleep(n);
    }
}

Я должен быть очевиден, основная проблема заключается в том, что предикат fSet не защищен вообще. Здесь многое может пойти не так. Пример: с момента, когда вы оцениваете пока-conditon до тех пор, пока вы не начнете ждать (или спиннинг или что-то еще), значение может измениться. Если это уведомление об изменении каким-то образом пропущено, вы не нуждаетесь в ожидании.

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

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

int WaitForTrue()
{
    pthread_mutex_lock(&mtx);
    while (!fSet)
        sleep(n);
    pthread_mutex_unlock(&mtx);
}

Ну, это кажется достаточно простым. Теперь мы никогда не оцениваем предикат, не получив к нему эксклюзивного доступа (путем фиксации мьютекса). Но это все еще большая проблема. Мы зафиксировали мьютекс, но мы никогда не выпускаем его, пока наша петля не будет закончена. Если все остальные играют по правилам и ждут блокировки мьютекса перед оценкой или модификацией fSet, они никогда не смогут этого сделать, пока не откажутся от мьютекса. Единственным "кем-то", который может это сделать в этом случае, является нас.

Как насчет добавления к этому еще большего количества слоев. Будет ли это работать?

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

int WaitForTrue()
{
    pthread_mutex_lock(&mtx);
    while (!fSet)
    {
        pthread_mutex_unlock(&mtx);
        // XXXXX
        sleep(n);
        // YYYYY
        pthread_mutex_lock(&mtx);
    }
    pthread_mutex_unlock(&mtx);
}

Ну, да, это будет "работать", но все же не намного лучше. Период между XXXXX и YYYYY у нас нет мьютекса (это нормально, поскольку мы все равно не проверяем и не изменяем fSet). Но в любое время в течение этого периода какой-то другой поток может (а) получить мьютекс, (б) изменить fSet и (c) освободить мьютекс, и мы ничего не узнаем об этом, пока не закончим наш sleep(), один раз снова получите блокировку мьютекса и зациклируйте для другой проверки.

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


Переменная условия в действии

Введите пару переменная condition-variable + mutex. Мьютекс защищает доступ к изменению или проверке предиката, в то время как переменная условия устанавливает систему мониторинга изменения и, что более важно, делает это атомарно (насколько вам известно, во всяком случае) с предикатным взаимным исключением:

int WaitForPredicate()
{
    // lock mutex (means:lock access to the predicate)
    pthread_mutex_lock(&mtx);

    // we can safely check this, since no one else should be 
    // changing it unless they have the mutex, which they don't
    // because we just locked it.
    while (!predicate)
    {
        // predicate not met, so begin waiting for notification
        // it has been changed *and* release access to change it
        // to anyone wanting to by unlatching the mutex, doing
        // both (start waiting and unlatching) atomically
        pthread_cond_wait(&cv,&mtx);

        // upon arriving here, the above returns with the mutex
        // latched (we own it). The predicate *may* be true, and
        // we'll be looping around to see if it is, but we can
        // safely do so because we own the mutex coming out of
        // the cv-wait call. 
    }

    // we still own the mutex here. further, we have assessed the 
    //  predicate is true (thus how we broke the loop).

    // take whatever action needed. 

    // You *must* release the mutex before we leave. Remember, we
    //  still own it even after the code above.
    pthread_mutex_unlock(&mtx);
}

Для какого-то другого потока, чтобы сигнализировать цикл выше, есть несколько способов сделать это, два самых популярных ниже:

pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cv);

Другой способ...

pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);

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

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


Простая тема монитора

Этот тип операций распространен в потоке монитора, который действует на конкретное условие предиката, которое (проверка ошибок sans) обычно выглядит примерно так:

void* monitor_proc(void *pv)
{
    // acquire mutex ownership
    //  (which means we own change-control to the predicate)
    pthread_mutex_lock(&mtx);

    // heading into monitor loop, we own the predicate mutex
    while (true)
    {
        // safe to check; we own the mutex
        while (!predicate)
            pthread_cond_wait(&cv, &mtx);

        // TODO: the cv has been signalled. our predicate data should include
        //  data to signal a break-state to exit this loop and finish the proc,
        //  as well as data that we may check for other processing.
    }

    // we still own the mutex. remember to release it on exit
    pthread_mutex_unlock(&mtx);
    return pv;
}

Более сложный поток монитора

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

void* monitor_proc(void *pv)
{
    // acquire mutex ownership
    //  (which means we own change-control to the predicate)
    pthread_mutex_lock(&mtx);

    // heading into monitor loop, we own the predicate mutex
    while (true)
    {
        // check predicate
        while (!predicate)
            pthread_cond_wait(&cv, &mtx);

        // some state that is part of the predicate to 
        // inform us we're finished
        if (break-state)
            break;

        // TODO: perform latch-required work here.

        // unlatch the mutex to do our predicate-independant work.
        pthread_mutex_unlock(&mtx);

        // TODO: perform no-latch-required work here.

        // re-latch mutex prior to heading into wait
        pthread_mutex_lock(&mtx);            
    }

    // we still own the mutex. remember to release it on exit
    pthread_mutex_unlock(&mtx);
    return pv;
}

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

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

Ответ 3

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