【Linux C++】条件变量wait函数

17 篇文章 1 订阅
8 篇文章 1 订阅

目录

前言

回顾

        条件变量

        条件变量操作函数        

        注意事项

条件变量的wait函数

为什么条件变量一定要搭配互斥锁使用?

总结

结语

封面


前言

        在《线程同步》这篇文章里讲过条件变量实现线程同步,但是只是模糊地讲了一下条件变量函数的基本用法,这篇文章将详细讲解一下条件变量的wait函数。看过之前那篇文章的可以直接跳转到条件变量的wait函数

回顾

        条件变量

        条件变量操作函数 
       

        pthread_cond_t cond;                                // 生命条件变量

        int pthread_cond_init();                              // 初始化条件变量

        int pthread_cond_destroy();                       // 销毁条件变量

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;        // 声明并初始化

        int pthread_cond_wait();                            // 等待被唤醒

        int pthread_cond_timedwait();                   // 等待被唤醒,带超时机制

        int pthread_cond_signal();                         // 唤醒一个等待中的线程

        int pthread_cond_broadcast();                   // 唤醒全部等待中的线程

                 

        // 设置条件变量的共享属性,也就是在多个进程的线程之间是否共享条件变量

        int pthread_condattr_getpshared();            // 获取共享属性

        int pthread_condattr_setpshared();            // 设置共享属性

        // 设置条件变量的时钟属性,一般用不到

        int pthread_condattr_getclock();                 // 获取时钟属性

        int pthread_condattr_setclock();                 // 设置时钟属性

        注意事项


        首先大家不要被条件变量的名称给误导了,大家就把它当作是一种特殊的锁就行了

        另外条件变量必须搭配互斥锁使用,至于为什么以后会讲,大家也可以先猜测猜测是为什么

        条件变量的wait()函数会产生阻塞

        

        我们接下来要讲的就是

        int pthread_cond_wait();                            // 等待被唤醒

        这个函数

条件变量的wait函数

        pthread_cond_wait(&cond,&mutex)

        首先可以看到它有两个参数,一个是条件变量,一个是互斥锁,这是系统的设定。至于为什么下面会讲。

        该函数有三个动作:

        1、把互斥锁解锁

        2、阻塞、等待条件信号(被唤醒)

        3、条件被触发+给互斥锁加锁(这两个操作是原子性的)

        demo程序验证:

        demo13.cpp:

// 本程序用于验证条件变量wait函数发生了什么
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;        // 声明并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;     // 声明并初始化互斥锁

void *thmain1(void *arg);                // 线程1主函数
void *thmain2(void *arg);                // 线程2主函数
 
int main(int argc, char* argv[])
{
    pthread_t thid1,thid2;               // 线程1、2的id

    // 创建线程
    if(pthread_create(&thid1,0,thmain1,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1);       // 延时一秒错开线程运行 
    if(pthread_create(&thid2,0,thmain2,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待子线程退出
    pthread_join(thid1,NULL); pthread_join(thid2,NULL);

    // 退出前销毁条件变量与互斥锁
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}

void *thmain1(void *arg)
{
    printf("线程1开始申请互斥锁...\n");
    pthread_mutex_lock(&mutex);
    printf("线程1申请互斥锁成功...\n\n");
sleep(5);
    printf("线程1开始等待条件信号...\n");
    pthread_cond_wait(&cond,&mutex);
    printf("线程1等待条件信号成功...\n\n");
}

void *thmain2(void *arg)
{
    printf("线程2开始申请互斥锁...\n");
    pthread_mutex_lock(&mutex);
    printf("线程2申请互斥锁成功...\n\n");

    printf("线程2开始等待条件信号...\n");
    pthread_cond_wait(&cond,&mutex);
    printf("线程2等待条件信号成功...\n\n");
}

        解释一下代码,首先申请互斥锁和条件变量,因为条件变量必须搭配互斥锁。然后线程1创建1秒后线程2才创建,这样的话,线程1就会先运行线程主函数获得互斥锁。在线程1获取互斥锁一秒之后,线程2开始申请,这个时候是肯定不成功的。再过4秒之后,线程1开始等待条件信号,注意!这个时候如果线程2申请互斥锁成功了,就代表条件变量的wait函数释放了锁。

        makfile文件如下:

demo13:demo13.cpp
    g++ -g -o demo13 demo13.cpp -lpthread

        编译运行

        可以看到线程1开始等待条件信号之后线程2的互斥锁就申请成功了。

        这证明了条件变量wait函数的前两个观点

        第二个观点运行的时候你会发现wait函数确实产生了阻塞,因为没有输出“等待条件信号成功”的字样 。

        下面证明条件出发和加锁是原子性的。

        demo14.cpp:

// 本程序用于验证条件变量wait函数发生了什么
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;        // 声明并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;     // 声明并初始化互斥锁

void *thmain1(void *arg);                // 线程1主函数
void *thmain2(void *arg);                // 线程2主函数
 
int main(int argc, char* argv[])
{
    pthread_t thid1,thid2;               // 线程1、2的id

    // 创建线程
    if(pthread_create(&thid1,0,thmain1,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1);       // 延时一秒错开线程运行 
    if(pthread_create(&thid2,0,thmain2,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待子线程退出
    pthread_join(thid1,NULL); pthread_join(thid2,NULL);

    // 退出前销毁条件变量与互斥锁
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}

void *thmain1(void *arg)
{
    printf("线程1开始申请互斥锁...\n");
    pthread_mutex_lock(&mutex);
    printf("线程1申请互斥锁成功...\n\n");

    printf("线程1开始等待条件信号...\n");
    pthread_cond_wait(&cond,&mutex);
    printf("线程1等待条件信号成功...\n\n");
}

void *thmain2(void *arg)
{
    printf("线程2开始申请互斥锁...\n");    
    pthread_mutex_lock(&mutex);            // 线程1等待条件信号的时候,线程2申请锁肯定会成功
    printf("线程2申请互斥锁成功...\n\n");

    printf("发送条件信号...\n");
    pthread_cond_signal(&cond);            // 线程2申请锁成功之后给线程1发送条件信号
    
    sleep(5);

    printf("线程2解锁...\n");
    pthread_mutex_unlock(&mutex);          
}

        代码是这样的:首先线程1获取锁,然后又调用条件变量wait函数,这个时候会解锁,所以线程2申请锁肯定会成功。线程2申请锁成功之后,给线程1发送条件信号,这个时候的wait函数收到了条件信号是立即返回呢?还是说等线程2解锁了之后wait函数才返回呢?如果当线程2发送条件信号之后线程1的wait函数就继续往下运行,则说明观点三并不是原子性的。如果线程2发送条件信号之后再解锁这个时候wait函数才继续往下运行,则说明wait函数收到条件信号和加锁是原子性操作。

        makefile不变

        编译运行:

         可以看到是线程2解锁了之后,条件变量的wait函数才返回,不然就会一直阻塞。所以观点三原子性成立。

为什么条件变量一定要搭配互斥锁使用?

        首先,这是程序的设定,但是程序的设定一定有他自己的道理,我认为是这样的:首先我们从条件变量的使用就可以知道,他一定是需要用互斥锁的,但是我们之前讲过的互斥锁有两个,一个mutex互斥锁,一个spinlock自旋锁。

        那她为什么选择了mutex互斥锁而没有选择spinlock自旋锁呢?只要你看过我的《线程同步》这篇文章应该就能知道。我们说过互斥锁在等待锁的过程中会休眠,不会消耗cpu,但是自旋锁在等待的过程中会不断地判断锁是否可用,是很消耗cpu的。而在条件变量的wait函数等待条件信号的这个过程中,时间是很长的,在生产消费者模型中,如果生产者不生产内容,wait函数就不会收到条件信号,这样的话如果使用spinlock就会消耗大量的cpu,但是mutex不会,因为它休眠了。

总结

        从上面的文章我们就很清楚条件变量的wait函数了,所以以后使用这个函数的时候一定要谨慎,要知道它会把互斥锁解开、会产生阻塞、并且收到信号和收回锁必须是原子性的。

结语

        还是和以前一样,文章有错的话,请大佬一定指出!谢谢大家!

封面

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值