我想使用一个线程生成节点,另一个线程操纵它。我有以下代码。它停留在生产者的一半循环中,从不执行消费者。我希望这两个for循环可以并行执行。
typedef struct _Net {
int id;
struct _Net * next;
unsigned long net;
} Net;
typedef Net * NetPtr;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
NetPtr prevQueue[11];
int total = 0;
NetPtr queue;
void *producer(){
int i;
NetPtr prev, netNode;
prev = (Net *)malloc(sizeof(Net));
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
for (i = 0; i<10 ; i++) {
netNode = (Net *)malloc(sizeof(Net));
(*netNode).id = i;
pthread_mutex_lock(&mutex);
prevQueue[i] = netNode;
total += 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
void *consumer(){
int i;
while(total == 0)
{
pthread_cond_wait(&cond,&mutex);
}
for(i = 0; i<10; i++){
pthread_mutex_lock(&mutex);
printf("%d\n", prevQueue[i]->id);
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char **argv) {
int tid1, tid2;
pthread_t thread[2];
pthread_attr_t attributes;
pthread_attr_init(&attributes);
if((tid1 = pthread_create(&thread[0],&attributes,producer,NULL)))
{
printf("\nError in the producer thread\n");
printf("\n");
}
if((tid2 = pthread_create(&thread[1],&attributes,consumer,NULL)))
{
printf("\nERror in the consumer thread\n");
}
pthread_join(thread[0],NULL);
pthread_join(thread[1],NULL);
return 0;
}
答案 0 :(得分:1)
作为初步事项,对于生产者来说,通过锁定互斥锁然后立即再次解锁它来开始并没有多大帮助。它不是有害的,但也没有用,至少在你的情况下是这样。
但是,您的程序确实存在一些问题。首先,pthread_cond_wait()
必须仅由保持指定互斥锁的线程调用。您的消费者线程在调用之前无法锁定互斥锁。
此外,您的代码似乎是在假设如果一个线程释放互斥锁而另一个线程在锁定该互斥锁时被阻止的情况下编写的,则被阻塞的线程将立即获取该互斥锁并继续。这绝不是保证。您的生产者线程或您的消费者线程可能并且很可能经常在解锁后立即重新锁定互斥锁,而不会让对方有机会运行。这种问题是信号量和条件变量的用途;你可以使用你已经拥有的条件变量来实现这个目的。
但是,在使用条件变量时,您需要注意突然的唤醒,并且还需要注意在没有线程等待条件变量时发送的缺失通知。正常的习惯用法是测试条件变量在等待唤醒后保护的条件,如果不满足则再次等待。我看到你实际上是这样做的 - 问题在于你所等待的条件并不是你想要等待的条件。
让我们分析一下你的问题。您需要同步对数组prevQueue
所体现的共享状态的访问,并提供共享变量total
以传达该数组的哪些元素已由生产者填充。生产者只在持有互斥体的情况下修改共享状态,这是好的,并且每次添加一个元素时它都会发出条件变量(仍然保持互斥锁)的信号,这也很好。
现在考虑消费者。除了上面描述的共享状态之外,它还具有本地状态记录,这是它消耗的下一个元素。那么,它可以取得进展的条件是什么?如果要使用的下一个元素是已经生成的元素之一,它可以进行。这是它需要等待的条件,它可能需要多次这样做,因为它第一次能够取得进展可能是在生产者产生它打算做的所有元素之前。另一方面,当消费者确定它可以取得进展时,它可以取得多大进展?很明显,它可以消耗尽可能多的元素,因为它可以肯定已经生成,这可能允许在再次等待之前消耗多个元素。
您的消费者线程功能的这个修改版本可以完成所有这些:
void *consumer() {
int i = 0;
int max = 0;
do {
/* we need to protect access to variable 'total' */
pthread_mutex_lock(&mutex);
/* wait, if necessary, for at least one more element to be produced */
while (max >= total) {
pthread_cond_wait(&cond,&mutex);
}
/* update how many elements are now available */
max = total;
/* allow the producer to continue while we consume available items */
pthread_mutex_unlock(&mutex);
while (i < max) {
/*
* Although prevQueue is shared, we know we can safely access
* elements up to index 'max', last-observed limit.
*/
printf("%d\n", prevQueue[i]->id);
i += 1;
}
} while (max < 10);
}
请特别注意,执行pthread_cond_wait()
时,指定的互斥锁(当前由调用者持有)是释放,允许其他线程锁定它,这对于一个线程很重要能够发出条件变量的信号。它将在呼叫返回之前重新锁定。