Linux系统编程系列之条件变量

一、什么是条件变量

        条件变量是一种同步互斥机制,通常与互斥锁一起使用以实现线程之间的通信和同步。

二、问题的引入

        先来看一个例子:小楠是一名在校学生,每个月都会从父母那里得到一笔生活费。现在她的钱花光了,想要去取钱。但是很显然取钱这样的事情不是想干就能干的,前提是卡里必须得有钱才行!于是小楠拿起手机一查发现:余额为¥0。现在她除了干瞪眼,唯一能干的事情也许只有一件:等。等到她爸妈汇了钱打电话通知她为止。

        但更进一步,即便是她爸妈汇了钱也打了电话通知了她,此刻她也不能一定保证能取到钱,因为与此同时她的众多兄弟姐妹(统统共用一个银行账号)很可能已经抢先一步将钱悉数取光了!因此当小楠收到爸妈的电话之后,需要再次确认是否有钱,才能取钱。

Linux系统编程系列之条件变量_第1张图片

三、使用场景

        (1)、 生产者-消费者模式:多个线程生产数据,多个线程消费数据。消费者等待条件变量,当生产者生产数据时唤醒消费者。

        (2)、 服务器程序:服务器可以使用条件变量来实现多个客户端之间的同步操作。当客户端请求数据时,服务器可能需要等待某些资源准备好后才能响应,此时可以使用条件变量来等待资源就绪。

        (3)、 任务管理:当有多个线程需要执行任务时,可以使用条件变量来通知空闲的线程执行任务

        (4)、 等待输入:当需要等待用户输入时,可以使用条件变量等待用户输入。

        总之,当需要等待某些条件满足时,使用条件变量是很常见的一种方式。条件变量提供了一个有效的机制来等待和通知多个线程,以实现共享资源间的同步和互斥

四、相关的函数API接口

        1、初始化条件变量

// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数cond:待初始化的条件变量
        参数cond_attr:待初始化的条件变量的属性,一般始终为0

         2、销毁条件变量

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

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数cond:待销毁的条件变量

        3、 进入等待队列

// 阻塞等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

// 接口说明
        返回值;成功返回0,失败返回-1
        参数cond:条件变量
        参数mutex:需要获取的互斥锁

// 有限定时间的等待
int pthread_cond_timedwait(pthread_cond_t *cond, 
                           pthread_mutex_t *mutex, 
                           const struct timespec *abstime);

// 接口说明
        返回值;成功返回0,失败返回-1
        参数cond:条件变量
        参数mutex:需要获取的互斥锁
        参数abstime:限定的时间

        4、唤醒等待队列 

// 唤醒全部在条件变量等待队列的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

// 唤醒一个在条件变量等待队列的线程
int pthread_cond_signal(pthread_cond_t *cond);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数cond:条件变量

五、案例

        使用条件变量结合互斥锁完成存钱和取钱的演示

// 条件变量的案例

#include 
#include 
#include 
#include 
#include 

int money = 0;

pthread_mutex_t data_mutex; // 定义互斥锁变量
pthread_once_t data_mutex_once_init;    // 函数单例初始化变量
pthread_once_t data_mutex_once_destroy;    // 函数单例销毁变量

pthread_cond_t data_cond; // 定义条件变量
pthread_once_t data_cond_once_init;    // 函数单例初始化变量
pthread_once_t data_cond_once_destroy;    // 函数单例销毁变量

// 初始化互斥锁data_mutex
void data_mutex_init(void)
{
    pthread_mutex_init(&data_mutex, NULL);
}

// 销毁互斥锁data_mutex
void data_mutex_destroy(void)
{
    pthread_mutex_destroy(&data_mutex);
}

// 初始化条件变量data_cond
void data_cond_init(void)
{
    pthread_cond_init(&data_cond, NULL);
}

// 销毁条件变量data_cond
void data_cond_destroy(void)
{
    pthread_cond_destroy(&data_cond);
}

// 线程1的例程函数,用来取钱
void *get_routine(void *arg)
{
    printf("I am recv_routine, my tid = %ld\n", pthread_self());

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_init, data_mutex_init);

    // 函数单例,本程序只会执行data_cond_init()一次
    pthread_once(&data_cond_once_init, data_cond_init);

    while(1)
    {
        printf("wait mutex to get money...\n");
        pthread_mutex_lock(&data_mutex);    // 阻塞等待有数据才可以申请成功,用来同步
        printf("get mutex to get money\n");

        // 判断余额是否大于100
        while(money < 100)
        {
            printf("get money fail, enter cond queue\n");

            // 这里先自动解锁data_mutex,等待被唤醒后,会自动上锁data_mutex
            pthread_cond_wait(&data_cond, &data_mutex);

            printf("wake up from cond queue\n");
        }

        money -= 100;
        printf("get money success\n");
        pthread_mutex_unlock(&data_mutex); // 解锁
     
        sleep(1); // 每隔1秒取100元
    }   

    // 函数单例,本程序只会执行data_mutex_destroy()一次
    pthread_once(&data_mutex_once_destroy, data_mutex_destroy);

    // 函数单例,本程序只会执行data_cond_destroy()一次
    pthread_once(&data_cond_once_destroy, data_cond_destroy);
}

// 线程2的例程函数,用来存钱
void *give_routine(void *arg)
{
    printf("I am send_routine, my tid = %ld\n", pthread_self());

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_init, data_mutex_init);

    // 函数单例,本程序只会执行data_cond_init()一次
    pthread_once(&data_cond_once_init, data_cond_init);


    while(1)
    {
        pthread_mutex_lock(&data_mutex);
        money += 200;

        // printf()应该放到临界区外面,但是为了演示效果所以放在这里
        printf("current money is %d\n", money); 

        pthread_mutex_unlock(&data_mutex);  // 解锁,相当于给线程1发送信号

        pthread_cond_broadcast(&data_cond); // 唤醒条件等待队列中等待的线程

        sleep(3); // 每隔3秒存200元
    }

   // 函数单例,本程序只会执行data_mutex_destroy()一次
    pthread_once(&data_mutex_once_destroy, data_mutex_destroy);

    // 函数单例,本程序只会执行data_cond_destroy()一次
    pthread_once(&data_cond_once_destroy, data_cond_destroy);
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;

    // 创建线程1,用来取钱
    errno = pthread_create(&tid1, NULL, get_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create get_routine success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create get_routine fail\n");
    }

    
    // 创建线程2,用来存钱,线程拥有分离属性
    errno = pthread_create(&tid2, NULL, give_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create give_routine success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create give_routine fail\n");
    }

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

Linux系统编程系列之条件变量_第2张图片

六、总结

        条件变量通常用于多线程间共享资源的同步访问,一般要配合互斥锁来使用,可以结合案例来加深对条件变量的理解。

你可能感兴趣的:(C语言程序设计,Linux,c语言,linux)