我正在尝试在pthread中编写一个简单的线程池程序。但是,pthread_cond_signal
似乎没有阻止,这会产生问题。例如,假设我有一个“生产者 - 消费者”计划:
pthread_cond_t my_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t my_cond_m = PTHREAD_MUTEX_INITIALIZER;
void * liberator(void * arg)
{
// XXX make sure he is ready to be freed
sleep(1);
pthread_mutex_lock(&my_cond_m);
pthread_cond_signal(&my_cond);
pthread_mutex_unlock(&my_cond_m);
return NULL;
}
int main()
{
pthread_t t1;
pthread_create(&t1, NULL, liberator, NULL);
// XXX Don't take too long to get ready. Otherwise I'll miss
// the wake up call forever
//sleep(3);
pthread_mutex_lock(&my_cond_m);
pthread_cond_wait(&my_cond, &my_cond_m);
pthread_mutex_unlock(&my_cond_m);
pthread_join(t1, NULL);
return 0;
}
正如两个XXX
标记所述,如果我取消sleep
次来电,则main()
可能因为错过了来自liberator()
的唤醒电话而失速。当然,sleep
并不是一种非常强大的方法来确保这两者。
在现实生活中,这将是一个工作线程告诉经理线程它已准备好工作,或者经理线程宣布新工作可用。
你如何在pthread中可靠地做到这一点?
@ Borealid的答案有点作品,但他对这个问题的解释可能会更好。我建议任何看这个问题的人阅读评论中的讨论,以了解发生了什么。
特别是,我自己会修改他的答案和这样的代码示例,以使其更清楚。 (因为Borealid的原始答案,虽然编译和工作,但我很困惑)
// In main
pthread_mutex_lock(&my_cond_m);
// If the flag is not set, it means liberator has not
// been run yet. I'll wait for him through pthread's signaling
// mechanism
// If it _is_ set, it means liberator has been run. I'll simply
// skip waiting since I've already synchronized. I don't need to
// use pthread's signaling mechanism
if(!flag) pthread_cond_wait(&my_cond, &my_cond_m);
pthread_mutex_unlock(&my_cond_m);
// In liberator thread
pthread_mutex_lock(&my_cond_m);
// Signal anyone who's sleeping. If no one is sleeping yet,
// they should check this flag which indicates I have already
// sent the signal. This is needed because pthread's signals
// is not like a message queue -- a sent signal is lost if
// nobody's waiting for a condition when it's sent.
// You can think of this flag as a "persistent" signal
flag = 1;
pthread_cond_signal(&my_cond);
pthread_mutex_unlock(&my_cond_m);
答案 0 :(得分:7)
使用同步变量。
在main
:
pthread_mutex_lock(&my_cond_m);
while (!flag) {
pthread_cond_wait(&my_cond, &my_cond_m);
}
pthread_mutex_unlock(&my_cond_m);
在帖子中:
pthread_mutex_lock(&my_cond_m);
flag = 1;
pthread_cond_broadcast(&my_cond);
pthread_mutex_unlock(&my_cond_m);
对于生产者 - 消费者问题,这将是消费者在缓冲区为空时休眠,并且生产者在其满时时休眠。请记住在访问全局变量之前获取锁。
答案 1 :(得分:4)
我找到了解决方案here。对我来说,理解这个问题的棘手问题在于:
为了说明,上面提到的博客文章证明了这实际上是有意义和理想的行为:
pthread_mutex_lock(&cond_mutex);
pthread_cond_broadcast(&cond):
pthread_cond_wait(&cond, &cond_mutex);
pthread_mutex_unlock(&cond_mutex);
这个想法是,如果生产者和消费者都使用这种逻辑,那么他们中的任何一个都可以安全地睡觉,因为每个人都能够唤醒另一个角色。换句话说,在一个典型的生产者 - 消费者场景中 - 如果消费者需要睡觉,那是因为生产者需要醒来,反之亦然。将此逻辑打包在单个pthread条件中是有道理的。
当然,上面的代码有一个意想不到的行为,即当一个工作线程实际上只想唤醒生产者时,它也会唤醒另一个睡眠工作线程。这可以通过@Borealid建议的简单变量检查来解决:
while(!work_available) pthread_cond_wait(&cond, &cond_mutex);
在工作人员广播时,所有工作线程都将被唤醒,但是一个接一个(因为pthread_cond_wait
中的隐式互斥锁定)。由于其中一个工作线程将消耗工作(将work_available
设置回false
),当其他工作线程唤醒并实际开始工作时,工作将不可用,因此工作人员将再次休眠。 / p>
以下是我测试的一些评论代码,对于任何感兴趣的人:
// gcc -Wall -pthread threads.c -lpthread
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
pthread_cond_t my_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t my_cond_m = PTHREAD_MUTEX_INITIALIZER;
int * next_work = NULL;
int all_work_done = 0;
void * worker(void * arg)
{
int * my_work = NULL;
while(!all_work_done)
{
pthread_mutex_lock(&my_cond_m);
if(next_work == NULL)
{
// Signal producer to give work
pthread_cond_broadcast(&my_cond);
// Wait for work to arrive
// It is wrapped in a while loop because the condition
// might be triggered by another worker thread intended
// to wake up the producer
while(!next_work && !all_work_done)
pthread_cond_wait(&my_cond, &my_cond_m);
}
// Work has arrived, cache it locally so producer can
// put in next work ASAP
my_work = next_work;
next_work = NULL;
pthread_mutex_unlock(&my_cond_m);
if(my_work)
{
printf("Worker %d consuming work: %d\n", (int)(pthread_self() % 100), *my_work);
free(my_work);
}
}
return NULL;
}
int * create_work()
{
int * ret = (int *)malloc(sizeof(int));
assert(ret);
*ret = rand() % 100;
return ret;
}
void * producer(void * arg)
{
int i;
for(i = 0; i < 10; i++)
{
pthread_mutex_lock(&my_cond_m);
while(next_work != NULL)
{
// There's still work, signal a worker to pick it up
pthread_cond_broadcast(&my_cond);
// Wait for work to be picked up
pthread_cond_wait(&my_cond, &my_cond_m);
}
// No work is available now, let's put work on the queue
next_work = create_work();
printf("Producer: Created work %d\n", *next_work);
pthread_mutex_unlock(&my_cond_m);
}
// Some workers might still be waiting, release them
pthread_cond_broadcast(&my_cond);
all_work_done = 1;
return NULL;
}
int main()
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, worker, NULL);
pthread_create(&t2, NULL, worker, NULL);
pthread_create(&t3, NULL, worker, NULL);
pthread_create(&t4, NULL, worker, NULL);
producer(NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
return 0;
}