Linux Pthread 条件变量

本文详细介绍了Linux Pthread中的条件变量,包括初始化、使用方法及其在多线程同步中的作用。通过分析条件变量在while和if中的使用区别,强调了在解决生产者消费者问题时,条件变量应配合while循环以避免资源竞争问题。文中给出了从单一到多个生产者消费者的示例,展示了条件变量的实战应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阅读了《Unix/Linux系统编程》一书中关于Pthread中的条件变量相关内容,进行了整理总结
本文包含了临界区、条件变量等内容,并关于pthread_cond_wait放在while还是if中通过示例进行了具体分析
参考资料

5. 条件变量

条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,即提供了一种线程协作的方式。

5.1 条件变量初始化

Pthreads中pthread_cond_t即为条件变量类型,与互斥量方法一样,条件变量有两种初始化方法。

  • 静态方法:定义一个条件变量con,并使用默认属性对其进行初始化

    pthread_cond_t con = PTHREAD_COND_INITIALIZER;
    
  • 动态方法:使用pthread_cond_init()函数来初始化条件变量,通过第二个参数设置条件变量属性。第二个参数可以设置为NULL来使用默认属性初始化条件变量

    int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t *attr)
    
    pthread_cond_init(&con,NULL);
    

5.2 条件变量使用:

  • 等待条件变量函数:pthread_cond_wait

    int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex)
    

    当线程调用pthread_cond_wait()函数时,该函数会阻塞调用线程,直到发出指定条件的信号。会将调用线程放到等待条件的线程列表上,并对互斥量解锁(这样就不会死锁)。当pthread_cond_wait()返回时(即收到信号后),互斥量再次锁住

    因为要根据共享变量的状态来决定是否等待(即互斥的访问共享变量),因此在该函数调用前要先获取相关互斥量(即正在互斥的访问该共享变量)。所以该函数必须要放在pthread_mutex_lockpthread_mutex_unlock之间的临界区内。

    该函数放在if还是while里面:

    需要注意由于有可能同时唤醒多个正在等待该条件的线程。因此,如果pthread_cond_wait放在if里面而不是while里面,那么当被唤起两个线程时(考虑争夺仅有的一个资源时的情况),会发生第二个线程没有资源可以消费的情况,后续操作出现错误!因此,一定要使用while做进一步循环判断,这样即使两个线程都被唤起,那么第二个线程会再次判断资源是否可用,如果不可用,就再次pthread_cond_wait。如

    while(resource == FALSE)
          pthread_cond_wait(&cond, &mutex)
    

    这个例子将放在本节第二个示例中具体分析

  • 通知线程条件满足:pthread_cond_signalpthread_cond_broadcast

    int pthread_cond_signal(pthread_cond_t * cond)
    

    该函数发出信号唤醒至少一个正在等待该条件的线程

    pthread_cond_signal既可以放在pthread_mutex_lockpthread_mutex_unlock之间**(推荐)**,也可以放在pthread_mutex_lockpthread_mutex_unlock之后,但是各有各的缺点。推荐使用第一种

    int pthread_cond_broadcast(pthread_cond_t * cond)
    

    该函数会唤醒等待该条件的所有线程

5.3 条件变量使用示例:简单(单一)生产者消费者问题

定义一个int类型全局变量num,并指定其上限。生产者线程不停往里面+1直到加到上限,然后就不能加了,要等消费者减后才能继续加;消费者线程不停往里面-1直到减到0,然后就不能减了,要等生产者加后才能继续减

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

#define MAX 5//共享变量最大值上界
int num;//全局共享变量

pthread_mutex_t * m;//互斥量
pthread_cond_t * push;//条件变量push:表示生产者生产了一个产品
pthread_cond_t * pop;//条件变量pop:表示消费者消费了一个产品

//消费者线程入口函数
void* consumer(void*arg){
    for(int i = 0 ; i < 15 ; ++i){
        pthread_mutex_lock(m);//上锁
        if(num == 0){
            pthread_cond_wait(push,m);//没有产品了,阻塞等待push信号唤醒
        }
        num--;
        printf("the num is %d now\n",num);
        pthread_cond_signal(pop);//消费了一个产品,发出pop信号
        pthread_mutex_unlock(m);//解锁
    }
}
//生产者线程入口函数
void* producer(void*arg){
    for(int i = 0 ; i < 15 ; ++i){
        pthread_mutex_lock(m);//上锁
        if(num == MAX){
            pthread_cond_wait(pop,m);//产品满了,阻塞等待pop信号唤醒
        }
        num++;
        printf("the num is %d now\n",num);
        pthread_cond_signal(push);//生产了一个产品,发出push信号
        pthread_mutex_unlock(m);//解锁
    }
}

int main(){
    m = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
    
    push = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
    pop = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
    
    pthread_mutex_init(m,NULL);//初始化互斥量
    pthread_cond_init(push,NULL);//初始化条件变量
    pthread_cond_init(pop,NULL);
    
    pthread_t producerThread;
    pthread_create(&producerThread,NULL,producer,NULL);//创建生产者线程
    pthread_t consumerThread;
    pthread_create(&consumerThread,NULL,consumer,NULL);//创建消费者线程

    void* status;
    pthread_join(producerThread,&status);//阻塞等待线程终止
    pthread_join(consumerThread,&status);
    printf("\nthe num finally is %d\n",num);
}

运行结果:可以看出全局变量num始终在0-MAX之间,通过互斥量和条件变量成功解决了单一生产者消费者问题

xtark@xtark-vmpc:~/桌面/linux_study/section4/pthread test$ gcc condTest.c -lpthread
xtark@xtark-vmpc:~/桌面/linux_study/section4/pthread test$ ./a.out 
the num is 1 now
the num is 2 now
the num is 3 now
the num is 4 now
the num is 5 now
the num is 4 now
the num is 3 now
the num is 2 now
the num is 1 now
the num is 0 now
the num is 1 now
the num is 2 now
the num is 3 now
the num is 4 now
the num is 5 now
the num is 4 now
the num is 3 now
the num is 2 now
the num is 1 now
the num is 0 now
the num is 1 now
the num is 2 now
the num is 3 now
the num is 4 now
the num is 5 now
the num is 4 now
the num is 3 now
the num is 2 now
the num is 1 now
the num is 0 now

the num finally is 0

5.4 条件变量使用示例:上一个示例的强化版本–同时多个生产者和消费者

定义一个int类型全局变量num,并指定其上限。创建20个生产者线程不停往里面+1直到加到上限,然后就不能加了,要等消费者减后才能继续加;创建20个消费者线程不停往里面-1直到减到0,然后就不能减了,要等生产者加后才能继续减。

在下面的程序中将pthread_cond_wait函数放在if里面,将会导致问题,结果在后面显示

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

#define MAX 5//共享变量最大值上界
int num;//全局共享变量

pthread_mutex_t * m;//互斥量
pthread_cond_t * push;//条件变量push:表示生产者生产了一个产品
pthread_cond_t * pop;//条件变量pop:表示消费者消费了一个产品

//消费者线程入口函数
void* consumer(void*arg){
    for(int i = 0 ; i < 15 ; ++i){
        pthread_mutex_lock(m);//上锁
        if(num == 0){
            pthread_cond_wait(push,m);//没有产品了,阻塞等待push信号唤醒
        }
        num--;
        printf("the num is %d now\n",num);
        pthread_cond_signal(pop);//消费了一个产品,发出pop信号
        pthread_mutex_unlock(m);//解锁
    }
}
//生产者线程入口函数
void* producer(void*arg){
    for(int i = 0 ; i < 15 ; ++i){
        pthread_mutex_lock(m);//上锁
        if(num == MAX){
            pthread_cond_wait(pop,m);//产品满了,阻塞等待pop信号唤醒
        }
        num++;
        printf("the num is %d now\n",num);
        pthread_cond_signal(push);//生产了一个产品,发出push信号
        pthread_mutex_unlock(m);//解锁
    }
}

int main(){
    m = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
    
    push = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
    pop = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
    
    pthread_mutex_init(m,NULL);//初始化互斥量
    pthread_cond_init(push,NULL);//初始化条件变量
    pthread_cond_init(pop,NULL);
    
    pthread_t producerThread[20];
    for(int i = 0 ; i < 20 ; ++i){//创建20个生产者线程
        pthread_create(&producerThread[i],NULL,producer,NULL);
    }
    
    pthread_t consumerThread[20];
    for(int i = 0 ; i < 20 ; ++i){//创建20个消费者线程
        pthread_create(&consumerThread[i],NULL,consumer,NULL);
    }
    
    void* status;
    for(int i = 0 ; i < 20 ; ++i){
        pthread_join(producerThread[i],&status);//阻塞等待线程终止
        pthread_join(consumerThread[i],&status);
    }

    printf("\nthe num finally is %d\n",num);
}

运行结果(部分):

xtark@xtark-vmpc:~/桌面/linux_study/section4/pthread test$ gcc condTest.c -lpthread
xtark@xtark-vmpc:~/桌面/linux_study/section4/pthread test$ ./a.out 
the num is 1 now
the num is 2 now
the num is 3 now
the num is 4 now
the num is 5 now
the num is 4 now
the num is 3 now
the num is 2 now
the num is 1 now
the num is 0 now
the num is 1 now
the num is 2 now
the num is 3 now
the num is 4 now
the num is 5 now
the num is 6 now
the num is 7 now
the num is 8 now
the num is 9 now
the num is 10 now

可以看出num的值会超出0-MAX范围,这就是将pthread_cond_wait函数放在if里面导致的问题:有可能同时有多个线程被唤醒,而他们都要用同一块资源。那么会发生第二个及之后的线程没有资源可以使用情况。因此,一定要使用while做进一步循环判断,这样即使多个线程都被唤起,那么第二个及之后的线程会再次判断资源是否可用,如果不可用,就再次pthread_cond_wait

将生产者和消费者进程的入口函数中的pthread_cond_wait函数调用放在while循环中即可

//消费者线程入口函数
void* consumer(void*arg){
    for(int i = 0 ; i < 15 ; ++i){
        pthread_mutex_lock(m);//上锁
        while(num == 0){//循环判断资源是否可用
            pthread_cond_wait(push,m);//没有产品了,阻塞等待push信号唤醒
        }
        num--;
        printf("the num is %d now\n",num);
        pthread_cond_signal(pop);//消费了一个产品,发出pop信号
        pthread_mutex_unlock(m);//解锁
    }
}
//生产者线程入口函数
void* producer(void*arg){
    for(int i = 0 ; i < 15 ; ++i){
        pthread_mutex_lock(m);//上锁
        while(num == MAX){//循环判断资源是否可用
            pthread_cond_wait(pop,m);//产品满了,阻塞等待pop信号唤醒
        }
        num++;
        printf("the num is %d now\n",num);
        pthread_cond_signal(push);//生产了一个产品,发出push信号
        pthread_mutex_unlock(m);//解锁
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值