试图了解pthread_cond_lock和pthread_cond_signal

时间:2018-10-24 03:26:25

标签: c pthreads

所以我试图确切地了解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检查循环的条件是否为true。如果是,则发送cond_signal并阻塞线程,使其无法再处理任何数据。

我确实很难解决这个问题,我希望能获得一些输入和反馈,以了解其工作原理以及是否根据上面的内容开始理解这一点。

我已经看过this个帖子,但仍然遇到问题

2 个答案:

答案 0 :(得分:2)

首先是摘要:

  • 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的情况下进行调用。

    调用线程将暂时释放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. */
};

线程工作器函数接收一个指向上述结构的指针。每个线程迭代一次循环,然后等待条件变量。唤醒时,如果完成标志仍为零,则线程将迭代循环。否则,线程退出。

/* 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,则可以使用以下命令将其编译为examplegcc -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(含)之间的值。 (因此,您可以使用信号量表示待处理作业的数量,但可能不表示每个/所有线程工作人员完成的迭代次数。)线程中的工作队列泳池时尚是最常见的方法。

答案 1 :(得分:1)

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)时发出信号只是性能优化,如果您总是在添加项目后发出信号,则代码也将正常工作。