C++多线程:条件变量condition_variable的使用与实现原理(八)

1、条件变量
  • 在 C++ 11中提供了条件变量(condition_variable)实现多个线程之间的同步操作

  • 当条件不满足时,相关线程一直被阻塞,直到某种条件成立,这些线程才会被唤醒。

  • 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包含两个动作:

    • 一个线程因为等待条件变量的条件成立而挂起,
    • 另外一个线程使条件成立,给出信号,从而唤醒被等待的线程。
  • 为了防止竞争,条件变量总是和一个互斥锁mutex一起使用。

  • 条件变量包含在#include <condition_variable>头文件之中

# 定义一个条件变量
std::condition_variable conditionVariable;

# 返回句柄
conditionVariable.native_handle();

# 通知阻塞在同一把互斥锁mutex上的所有线程
conditionVariable.notify_all();

#  通知阻塞在同一把互斥锁mutex上的一个线程
conditionVariable.notify_one();

# 阻塞在锁上等待notify_one/all的通知
conditionVariable.wait();

# 阻塞在锁上等待notify_one/all的通知,或到达指定的超时时长
conditionVariable.wait_for();

# 阻塞在锁上等待notify_one/all的通知,或到达指定的时间点
conditionVariable.wait_until();
2、wait系列函数
# 阻塞在锁上等待notify_one/all的通知
conditionVariable.wait();

# 阻塞在锁上等待notify_one/all的通知,或到达指定的超时时长
conditionVariable.wait_for();

# 阻塞在锁上等待notify_one/all的通知,或到达指定的时间点
conditionVariable.wait_until();
  • 这三个函数的构造函数首先第一个参数都是固定,传入std::unique_lock<std::mutex>的类对象

  • 另外两个wait_for和wait_until还可以传入时间点或者一个时间片,到达这个时间之后自动释放锁

  • 最后三个函数都可以传入一个函数,该函数(lambda表达式)返回值为true或者false。如果不传入也没事,默认为false。

    • 如果传入函数或者lambda表达式返回值为true,那么wait*()将会直接返回
    • 如果传入函数或者lambda表达式返回值为false,那么wait*()将会解锁互斥量mutex并且阻塞在本行
      • 对于阻塞在本行三个函数的效果不同:
      • wait将会一直死等、死等到notify_one或者notify_all的通知才会重新尝试加锁
      • wait_for会等一个时间片(例如15分钟后)或者收到notify_one或者notify_all的通知才会重新尝试加锁
      • wait_until会等一个指定时间点(例如11:00)或者收到notify_one或者notify_all的通知才会重新尝试加锁
  • 当收到notify的通知后或者到达时间点或者时间片,wait系列函数开始干活

    • 重新加锁,如果重新加锁失败继续释放锁然后继续阻塞

    • 加锁成功:

      • 如果调用的函数或者lambda表达式返回为false,那么继续释放锁,继续等待

      • 如果调用的函数或者lambda表达式返回为true向下执行代码

      • 如果没有传入函数或者lambda表达式,那么为true,继续向下执行代码

      • 执行完毕临界区代码需要及时的释放掉锁,以防造成死锁

  • 在wait系列函数调用之前首先需要加锁,在对wait函数进行调用,然后再判断条件是否成立,不成立就释放,成立就继续持有!

3、notify系列函数

notify系列拢共2个函数,尝试唤醒线程的数量不同。

  • notify_one:至多通知一个线程,

  • notify_all:至多通知n个线程

  • 至多这个词很奇怪,可以换句话来描述:负责叫醒所有持有同一把互斥锁的线程,但有没有用就不知道了。因此它只的工作只是尝试!

  • 如果没有线程阻塞在条件变量的锁上,那么通知就是无效的。因此才需要加入至多这个词

# 通知阻塞在同一把互斥锁mutex上的所有线程
conditionVariable.notify_all();

#  通知阻塞在同一把互斥锁mutex上的一个线程
conditionVariable.notify_one();
4、Demo举例子
  • 核心内容就是in和out函数中的notify和wait,但是不一定notify是有效的,有可能需要通知的wait线程一个也没有。
  • condition_variable的核心其实就是基于Linux系统下的pthread_cond_t的一个变量
#include <iostream>
#include <mutex>
#include <thread>
#include <queue>
#include <condition_variable>


class MessageQueue{
public:
    void inMsgRecvQueue(){
        for(int i = 0;i < n;i++){
            std::unique_lock<std::mutex> uniqueLock(mutex_lock);
            std::cout << "inMsgRecvQueue()执行, 插入一个元素i = " << i << std::endl;
            q.push(i);
            conditionVariable.notify_one();
        }
    }

    void outMsgRecvQueue(){
        while(true){
            std::unique_lock<std::mutex> uniqueLock(mutex_lock);
            conditionVariable.wait(uniqueLock, [this] () {
                return q.size() > 0;
            });
            int msg = q.front();
            q.pop();
            uniqueLock.unlock();
            std::cout << "outMsgRecvQueue()执行, msg = " << msg << std::endl;
        }
    }
private:
    std::queue<int> q;
    static const int n = 100000;
    std::mutex mutex_lock;
    std::condition_variable conditionVariable;
};

void test_mutex_lock_unlock()
{
    MessageQueue messageQueue;
    std::thread in_thread(&MessageQueue::inMsgRecvQueue, std::ref(messageQueue));
    std::thread out_thread(&MessageQueue::outMsgRecvQueue, std::ref(messageQueue));

    in_thread.join();
    out_thread.join();
}
5、pthread_cond_t

C++11在Linux操作系统下就是基于pthread_cond_t变量进行实现的,加上一些模板与类对齐进行封装

条件变量:
条件变量是需要和信号互斥量mutex一起使用的一个线程同步的控制原语


// 定义一个条件变量, 并且使用静态初始化
1. pthread_cond_t condition = PTHREAD_COND_INITIALIZER;

// 动态初始化条件变量
2. int pthread_cond_init(pthread_cond_t *restrict condition, pthread_condattr_t *restrict attr);

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

// 带时间的阻塞等待
4. int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                              pthread_mutex_t *restrict mutex,
                              const struct timespec *restrict abstime); 
/*
    最复杂的是wait函数,首先wait函数需要执行很多任务
    1). wait函数调用之前首先需要获得信号互斥量的锁,即加锁操作
    2). 判断条件是否满足:
        1. 条件满足:首先获得了互斥锁,那么访问临界区(公共数据区域)
        2. 条件不满足:阻塞,注意这里阻塞在条件变量cond上,放弃锁(解锁)
           这也是为什么调用之前需要先加锁

    1) 和 2)是一个原子操作,不可分割即无法放弃cpu

    3). 当条件满足时阻塞的wait会尝试加锁,然后再访问临界区(公共数据区域)
 */

// 通知至少一个阻塞在条件上的线程
5. int pthread_cond_signal(pthread_cond_t *cond);

// 通知(广播)所有阻塞在条件上的线程
6. int pthread_cond_broadcast(pthread_cond_t *cond);

// 动态初始化的条件变量进行销毁
7. int pthread_cond_destroy(pthread_cond_t *cond);

所有的函数返回值为0表示正常,返回非0表示错误,返回的是错误号。
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值