Попытка понять pthread_cond_lock и pthread_cond_signal - PullRequest
0 голосов
/ 24 октября 2018

Итак, я пытаюсь понять, как именно работает pthread_mutex_lock.

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

Затем он ожидает, когда сигнал перейдет из спящего режима в заблокированный, что означает, что поток больше не может изменять какие-либо переменные.

thread 1:
    pthread_mutex_lock(&mutex);
    while (!condition){
        printf("Thread wating.\n");
        pthread_cond_wait(&cond, &mutex);
        printf("Thread awakened.\n");
        fflush(stdout);
   }
   pthread_mutex_unlock(&mutex);

   pthread_cond_signal(&condVar);
   pthread_mutex_unlock(&mutex);

Таким образом, в основном в приведенном выше примере цикл выполняется ивыполняется, и каждая итерация pthread_cond_wait проверяет, верно ли условие цикла.Если это так, то cond_signal отправляется, и поток блокируется, поэтому он не может больше манипулировать данными.

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

Я перешел эту публикацию, но все еще испытываю проблемы

Ответы [ 2 ]

0 голосов
/ 24 октября 2018

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

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

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

Вот несколько простых примеров кода.Сначала поток чтения:

// === Thread 1 ===

// We want to process an item from a list.
// To make sure the list is not altered by one
// thread while another thread is accessing it,
// it is protected by a mutex.
pthread_mutex_lock(&listLock);

// Now nobody but us is allowed to access the list.
// But what if the list is empty?
while (list->count == 0) {
    // As long as we hold the mutex, no other thread
    // thread can add anything to the list. So we
    // must release it. But we want to know as soon
    // as another thread has changed it.
    pthread_cond_wait(&listCondition, &listLock);

    // When we get here, somebody has signaled the
    // condition and we have the mutex again and
    // thus are allowed to access the list. The list
    // may however still be empty, as another thread
    // may have already consumed the new item in case
    // there are multiple readers and all are woken 
    // up, thus the while-loop. If the list is still
    // empty, we just go back to sleep and wait again.
}

// If we get here, the list is not empty.
processListItem(list);

// Finally we release the mutex again.
pthread_mutex_unlock(&listLock);

А затем поток записи:

// === Thread 2 ===

// We want to add a new item to the list.
// To make sure that nobody is accessing the
// list while we do, we need to obtain the mutex.
pthread_mutex_lock(&listLock);

// Now nobody but us is allowed to access the list.
// Check if the list is empty.
bool listWasEmpty = (list->count == 0);

// We add our item.
addListItem(list, newItem);

// If the list was empty, one or even multiple
// threads may be waiting for us adding an item.
// So we should wake them up here.
if (listWasEmpty) {
    // If any thread is waiting for that condition,
    // wake it up as now there is an item to process.
    pthread_cond_signal(&listCondition);
}

// Finally we must release the mutex again.
pthread_mutex_unlock(&listLock);

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

0 голосов
/ 24 октября 2018

Во-первых, сводка:

  • pthread_mutex_lock(&mutex):

    Если mutex свободен, то этот поток немедленно его захватывает.

    Если захватывается mutex, то этот поток ожидает, пока mutex не станет свободным, а затем захватывает его.

  • pthread_mutex_trylock(&mutex):

    Еслиmutex свободен, тогда этот поток захватывает его.

    Если захватить mutex, то вызов немедленно возвращается с EBUSY.

  • pthread_mutex_unlock(&mutex):

    Выпуски mutex.

  • pthread_cond_signal(&cond):

    Пробуждение одного потока в ожидании переменной условия cond.

  • pthread_cond_broadcast(&cond):

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

  • pthread_cond_wait(&cond, &mutex):

    Это необходимо вызвать с mutex grabbed.

    Вызывающая нить временно освободит mutex и будет ждать cond.

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

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


Давайте посмотрим на практический пример кода.Мы создадим его в соответствии с кодами OP.

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

#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <pthread.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Worker function work. */
struct work {
    pthread_t        thread_id;
    pthread_mutex_t *lock;      /* Pointer to the mutex to use */
    pthread_cond_t  *wait;      /* Pointer to the condition variable to use */
    volatile int    *done;      /* Pointer to the flag to check */
    FILE            *out;       /* Stream to output to */
    long             id;        /* Identity of this thread */
    unsigned long    count;     /* Number of times this thread iterated. */
};

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

/* Example worker function. */
void *worker(void *workptr)
{
    struct work *const work = workptr;

    pthread_mutex_lock(work->lock);

    /* Loop as long as *done == 0: */
    while (!*(work->done)) {
        /* *(work->lock) is ours at this point. */

        /* This is a new iteration. */
        work->count++;

        /* Do the work. */
        fprintf(work->out, "Thread %ld iteration %lu\n", work->id, work->count);
        fflush(work->out);

        /* Wait for wakeup. */
        pthread_cond_wait(work->wait, work->lock);
    }

    /* *(work->lock) is still ours, but we've been told that all work is done already. */
    /* Release the mutex and be done. */
    pthread_mutex_unlock(work->lock);
    return NULL;
}

Для выполнения вышеприведенного нам также понадобится main ():

#ifndef  THREADS
#define  THREADS  4
#endif

int main(void)
{
    pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t   wait = PTHREAD_COND_INITIALIZER;
    volatile int     done = 0;
    struct work      w[THREADS];

    char            *line = NULL, *p;
    size_t           size = 0;
    ssize_t          len  = 0;

    unsigned long    total;
    pthread_attr_t   attrs;
    int              i, err;

    /* The worker functions require very little stack, but the default stack
       size is huge. Limit that, to reduce the (virtual) memory use. */
    pthread_attr_init(&attrs);
    pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);

    /* Grab the mutex so the threads will have to wait to grab it. */
    pthread_mutex_lock(&lock);

    /* Create THREADS worker threads. */
    for (i = 0; i < THREADS; i++) {

        /* All threads use the same mutex, condition variable, and done flag. */
        w[i].lock = &lock;
        w[i].wait = &wait;
        w[i].done = &done;

        /* All threads output to standard output. */
        w[i].out = stdout;

        /* The rest of the fields are thread-specific. */
        w[i].id = i + 1;
        w[i].count = 0;

        err = pthread_create(&(w[i].thread_id), &attrs, worker, (void *)&(w[i]));
        if (err) {
            fprintf(stderr, "Cannot create thread %d of %d: %s.\n", i+1, THREADS, strerror(errno));
            exit(EXIT_FAILURE);  /* Exits the entire process, killing any other threads as well. */
        }
    }

    fprintf(stderr, "The first character on each line controls the type of event:\n");
    fprintf(stderr, "    e, q    exit\n");
    fprintf(stderr, "    s       signal\n");
    fprintf(stderr, "    b       broadcast\n");
    fflush(stderr);

    /* Let each thread grab the mutex now. */
    pthread_mutex_unlock(&lock);

    while (1) {
        len = getline(&line, &size, stdin);
        if (len < 1)
            break;

        /* Find the first character on the line, ignoring leading whitespace. */
        p = line;
        while ((p < line + len) && (*p == '\0' || *p == '\t' || *p == '\n' ||
                                    *p == '\v' || *p == '\f' || *p == '\r' || *p == ' '))
            p++;

        /* Do the operation mentioned */
        if (*p == 'e' || *p == 'E' || *p == 'q' || *p == 'Q')
            break;
        else
        if (*p == 's' || *p == 'S')
            pthread_cond_signal(&wait);
        else
        if (*p == 'b' || *p == 'B')
            pthread_cond_broadcast(&wait);
    }

    /* It is time for the worker threads to be done. */
    pthread_mutex_lock(&lock);
    done = 1;
    pthread_mutex_unlock(&lock);

    /* To ensure all threads see the state of that flag,
       we wake up all threads by broadcasting on the condition variable. */
    pthread_cond_broadcast(&wait);

    /* Reap all threds. */
    for (i = 0; i < THREADS; i++)
        pthread_join(w[i].thread_id, NULL);

    /* Output the thread statistics. */
    total = 0;
    for (i = 0; i < THREADS; i++) {
        total += w[i].count;
        fprintf(stderr, "Thread %ld: %lu events.\n", w[i].id, w[i].count);
    }
    fprintf(stderr, "Total: %lu events.\n", total);

    return EXIT_SUCCESS;
}

Если вы сохраните вышеприведенное как example.c,вы можете скомпилировать его в example, используя, например, gcc -Wall -O2 example.c -lpthread -o example.

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

Вы даже можете запускать такие команды, как printf '%s\n' s s s b q | ./example, чтобы выполнить последовательность событий в быстрой последовательности, или printf 's\ns\ns\nb\nq\n' | ./example с еще меньшим временем между событиями.

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

(Кроме того, если вы сигнализируете переменную условия и сразу транслируете ее, потоки, как правило, просыпаются только один раз.)

Вы можете уменьшить это, задержав выход, используя, например, (printf '%s\n' s s b s s s ; sleep 1 ; printf 'q\n' ) | ./example.

Однако есть и лучшие способы.Переменная условия не подходит для счетных событий;это действительно как флаг.Семафор будет работать лучше, но тогда вы должны быть осторожны, чтобы не переполнить семафор;оно может быть только от 0 до SEM_VALUE_MAX включительно.(Таким образом, вы можете использовать семафор для представления числа отложенных заданий, но, вероятно, не для количества итераций, выполненных каждым / всеми рабочими потока.) Очередь для работы, которую нужно выполнить, в пуле потоковмода, это самый распространенный подход.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...