使用条件变量多线程时出现意外行为

时间:2015-05-26 13:28:20

标签: c multithreading mutex condition-variable

在下面的代码中:

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

pthread_mutex_t mtx;
pthread_cond_t cond;

int how_many = 10;
int pool = 0;

void * producer(void * ptr)
{
        while (how_many > 0)
        {
                pthread_mutex_lock(&mtx);
                printf("producer: %d\n", how_many);
                pool = how_many;
                how_many--;
                pthread_mutex_unlock(&mtx);
                pthread_cond_signal(&cond);
        }

        pthread_exit(0);
}

void * consumer(void * ptr)
{
        while (how_many > 0)
        {
                pthread_mutex_lock(&mtx);
                pthread_cond_wait(&cond, &mtx);
                printf("consumer: %d\n", pool);
                pool = 0;
                pthread_mutex_unlock(&mtx);
        }
        pthread_exit(0);
}

int main(int argc, char ** argv)
{
        pthread_t prod, cons;
        pthread_mutex_init(&mtx, 0);
        pthread_cond_init(&cond, 0);
        pthread_create(&cons, 0, consumer, 0);
        pthread_create(&prod, 0, producer, 0);
        pthread_join(prod, 0);
        pthread_join(cons, 0);
        pthread_cond_destroy(&cond);
        pthread_mutex_destroy(&mtx);
        return 0;
}

我没有得到预期的输出。

预期输出:

Producer:10    
Consumer:10    
Producer:9    
Consumer:9    
Producer:8    
Consumer:8    
Producer:7    
Consumer:7    
Producer:6    
Consumer:6    
Producer:5    
Consumer:5    
Producer:4    
Consumer:4    
Producer:3    
Consumer:3    
Producer:2    
Consumer:2    
Producer:1    
Consumer:1

实际输出:

producer: 10    
producer: 9    
producer: 8    
producer: 7    
producer: 6    
producer: 5    
producer: 4    
producer: 3    
producer: 2    
producer: 1

另外,在消费者方面,如果我们锁定并等待信号,生产者如何获得锁定以便他可以将信号发送给消费者?

  1. 它会死锁吗?
  2. 我的朋友们建议像pthread_cond_wait(&cond, &mtx);实际上会释放资源,直到它从生产者那里获得信号。这是真的吗?

3 个答案:

答案 0 :(得分:3)

互斥锁只提供互斥(如果使用得当);它们本身不提供阻止特定事件发生或阻止特定条件的机制。那是什么条件变量(和信号量,如果你想要更低一级)。

您的代码允许消费者等待生产者生产,但生产者不能等待消费者在继续生产之前消费。如果您希望两个线程交替,那么您需要第二个条件变量来提供后者。

  

另外,在消费者方面,如果我们锁定并等待信号,生产者如何获得锁定以便他可以将信号发送给消费者?

     
      
  1. 它会死锁吗?

  2.   
  3. 我的朋友们建议像pthread_cond_wait(&amp; cond,&amp; mtx);实际上会释放资源,直到它从生产者那里获得信号。那是真的吗?

  4.   

您是否考虑过阅读文档,而不是询问您的朋友或互联网?这是手册页描述它的方式:

  

这些函数以原子方式释放互斥锁[...]。成功返回后,互斥锁应被锁定,并由调用线程拥有。

也就是说,调用pthread_cond_wait()的线程在等待时没有锁定互斥锁,但它在返回之前重新获取互斥锁(这可能涉及接收信号的线程与函数调用之间的不确定延迟)返回)。

此外,永远记住线程可以在等待条件变量时激动地醒来。必须检查唤醒是否确实满足条件,如果没有则恢复等待。

这是一种可以构建生产者的方法:

void * producer(void * ptr)
{
        pthread_mutex_lock(&mtx);
        while (how_many > 0)
        {
                if (pool == 0) {
                        printf("producer: %d\n", how_many);
                        pool = how_many;
                        how_many--;
                        pthread_cond_signal(&full_cond);
                }
                pthread_cond_wait(&empty_cond, &mtx);
        }
        pthread_mutex_unlock(&mtx);

        pthread_exit(0);
}

请注意:

  1. 我已重命名原始条件变量并引入了新变量。现在有full_cond,表示池(容量1)已满,empty_cond表示池为空。
  2. 整个循环受互斥锁保护。这很好,因为它执行pthread_cond_wait()命名互斥;其他线程将能够在生产者等待时运行。互斥锁可确保正确同步how_manypool变量的访问权。
  3. 循环通过测试pool来确保它确实为空,从而防止了激烈的唤醒。如果没有,它会循环回到等待而不做任何其他事情。
  4. 为了使其正常工作,消费者需要相应的更改(留作练习)。

答案 1 :(得分:2)

您正在检查锁定部分中的how_many。您需要对代码进行重组,以便读取变量或由锁定覆盖,或者使其为C11 _Atomic

即使这样,你的代码的输出可能也不会像你想要的那样,因为线程的调度几乎是不可预测的。

答案 2 :(得分:0)

对于您的预期输出,您可以使用如下所示的锁定机制

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

 sem_t mutex1; 
 sem_t mutex2; 
 int main()
 {
    pthread_t thread1, thread2; 
    sem_init(&mutex1, 0, 1); 
    sem_init(&mutex2, 0, 0);
    pthread_create( &thread1, NULL, &producer, NULL)
    pthread_create( &thread2, NULL, &consumer, NULL)
    pthread_join( thread1, NULL); 
    pthread_join( thread2, NULL); 
    return 0; 
 }


void producer()
{
  sem_wait(&mutex1); 
  :
  :

  sem_post(&mutex2);
}

void consumer ()
{
    sem_wait(&mutex2); 
    :
    : 
    sem_post(&mutex1);
}