调用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
预期的行为: 主线程持有互斥锁,仅在等待时释放它。然后,工作线程将写入其数据并向主线程发出信号。主线程将立即锁定互斥锁打开信号,然后读取数据并返回等待状态,释放互斥锁。同时,工作线程将完成其工作并一直等待,直到主线程再次等待写入其数据并发出信号为止。
相反,工作线程似乎在调用信号后立即获取互斥体,很少让主线程获得访问权限。如果我在工作线程中代替// Do some work
进行睡眠,那么它将提供预期的输出。
答案 0 :(得分:2)
对条件变量进行信号通知不会将互斥锁锁定到正在等待该条件变量的线程上的任何优先级。这意味着,至少有一个等待条件变量的线程将开始尝试获取互斥量,以便可以从pthread_cond_wait()
返回。如您所见,信令线程将继续执行并可以轻松地首先重新获取互斥体。
永远不要在没有正在等待的某些共享状态的实际条件的情况下使用条件变量-从pthread_cond_wait()
返回并不意味着一定要继续执行线程,这意味着它应检查是否它等待的条件是真实的。因此,它们被称为条件变量。
在这种情况下,您的编写线程要等待的状态是“主线程消耗了我编写的最后一个数据。” 。但是,您的读取(主)线程也需要等待以下条件-“写入线程已写入一些新数据” 。您可以使用标志变量来实现这两个条件,该标志变量指示某些新的,未使用的数据已写入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,因此是可以预期的。