pthread_cond_signal() 没有给信号线程足够的时间 运行

pthread_cond_signal() not giving enough time for the signaled thread to run

调用 pthread_cond_signal 的线程正在重新获取互斥量,然后才能释放信号线程。

下面的代码显示了手头问题的一个简单示例。主线程将持有锁,创建工作线程,然后进入一个循环,在数据进入时打印数据。它通过条件变量向 运行 发出信号。

工作线程会进入一个循环,产生数据,抢锁,写入数据,然后给主线程发信号。

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;

void *thread(void *arg) {
    int length = *(int *) arg;
    for (int i = 0; i < length; i++) {
        // Do some work
        pthread_mutex_lock(&lock);
        data = i;
        fprintf(stdout, "Writing\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}


int main(int argc, const char *argv[]) {
    pthread_t worker;
    int length = 4;

    pthread_mutex_lock(&lock);
    pthread_create(&worker, 0, thread, &length);

    for (int i = 0; i < length; i++) {
        fprintf(stdout, "Waiting\n");
        pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "read data: %d\n", data);
    }
    pthread_mutex_unlock(&lock);

    pthread_join(worker, NULL);
    return 0;
}

这将给出以下输出:

Waiting
Writing
Writing
Writing
Writing
read data: 3
Waiting

预期行为: 主线程持有互斥锁,只有在等待时才释放它。然后,工作线程将写入其数据并向主线程发出信号。主线程将立即锁定信号互斥量,然后读取数据并返回等待,释放互斥量。与此同时,工作线程将完成它的工作并一直等待,直到主线程再次等待写入它的数据并向它发出信号。

相反,工作线程似乎在调用信号后立即获取互斥锁,很少让主线程获得访问权限。如果我在工作线程中放置一个 sleep 代替 // Do some work 那么它将给出预期的输出。

向条件变量发出信号不会优先将互斥量锁定到等待该条件变量的线程。这意味着至少有一个等待条件变量的线程将开始尝试获取互斥锁,以便它可以从 pthread_cond_wait() return。信号线程将继续执行,并且可以轻松地首先重新获取互斥量,如您所见。

你不应该在你正在等待的某些共享状态上有一个没有实际条件的条件变量 - return从 pthread_cond_wait() 并不意味着线程一定要继续,这意味着它应该检查它正在等待的条件是否为真。这就是为什么它们被称为 条件变量

在这种情况下,您的写入线程要等待的状态是"the main thread has consumed the last data I wrote."。但是,您的阅读(主)线程 需要等待一个条件 - "the writing thread has written some new data"。您可以使用标志变量来实现这两个条件,该标志变量指示一些新的、未使用的数据已写入 data 变量。该标志开始时未设置,在更新 data 时由写入线程设置,在从 data 读取时由主线程取消设置。写入线程等待标志被取消设置,读取线程等待标志被设置。

通过这种安排,您也不需要在启动写入线程时锁定互斥量 - 线程启动的顺序无关紧要,因为无论哪种方式,一切都是一致的。

更新后的代码如下所示:

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;
int data_available = 0;

void *thread(void *arg)
{
    int length = *(int *) arg;
    for (int i = 0; i < length; i++) {
        // Do some work
        pthread_mutex_lock(&lock);
        fprintf(stdout, "Waiting to write\n");
        while (data_available)
            pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "Writing\n");
        data = i;
        data_available = 1;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}


int main(int argc, const char *argv[])
{
    pthread_t worker;
    int length = 4;

    pthread_create(&worker, 0, thread, &length);

    for (int i = 0; i < length; i++) {
        pthread_mutex_lock(&lock);
        fprintf(stdout, "Waiting to read\n");
        while (!data_available)
            pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "read data: %d\n", data);
        data_available = 0;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }

    pthread_join(worker, NULL);
    return 0;
}

当然,线程最终会同步工作 - 但本质上,您有一个最大队列长度为 1 的生产者-消费者,所以这是预期的。