线程(三):条件变量

条件变量的作用

在线程中, 如果有这么一种情况, 生产者,消费者.
假设用互斥锁的话, 线程一: 不断的写入数据, 线程二 不断的读取数据, 那么线程二基本是一个轮询, 不断的加锁,解锁,判断数据. 不断的轮询的结果, 浪费性能. 解决办法, 那就是间隔sleep 一定的时间,但是当条件满足的时候, 线程二还卡在sleep. 如果实时性高的话, 那就不是很理想.

用到的知识点:

std::mutex
std::unique_lock
std::condition_variable
wait()
notify_one()
_

先进入代码;后续在详细说具体出现的情况:

#include<iostream>
#include<condition_variable>
#include<thread>
#include<mutex>
#include<list>
#include<chrono>

std::mutex _mutex;
std::condition_variable condition;
std::list<int>_list;
/*
* 消费者
*  采用 std::unique_lock<std::mutex> lock(_mutex); 加锁,而不是 lock_guard<mutex> guard(_mutex); why ? 
* 1> 因为在消费者中, 取出数据, 下面就是对数据进行处理和计算了(线程安全), 这个时候已经不需要在对数据加锁了, 用 unique_lock 很灵活, 可以进行解锁, 如果有需要的时候在加锁,
* 2> 在调用wait 的时候, 当条件不满足的时候, 会解锁并阻塞等待, 当notify_one 触发wait()条件满足的时候加锁,(根据条件是否成立, 加锁解锁, 这就比lock_guard 灵活的多.)
* 
*/
void wait()
{
    for(;;)
    {
        std::unique_lock<std::mutex> lock(_mutex);				//1
        condition.wait(lock,[]{ return _list.size() !=0;});		//2
        int data = _list.front();
        _list.pop_front();
        std::cout<<"pop data:"<<data<<std::endl;
        lock.unlock();											
        //std::this_thread::sleep_for(std::chrono::milliseconds(20));   //3

    }

}
/*
*生产者
*
*/
void Signal()
{
    for(int i=0;i<1000;i++)
    {
        std::unique_lock<std::mutex> lock(_mutex); //1
        _list.push_back(i);
        std::cout<<"push data:"<<i<<std::endl;
        condition.notify_one();					  //2
    }//3 
}

int main()
{
    std::thread th(wait), th1(Signal);
    th.join();
    th1.join();
    return 0;
}

生产者:

步骤1> 获得锁,
步骤2>

notify_one();的作用类似一个触发条件, 让阻塞中的wait() 解绑,继续运行., 运行后进入3 ,解锁.
这会notify_one 只对阻塞中的 wait 有用, 当消费者处于4步骤中, 或者wait没有阻塞是不起作用的, 也就是所谓的虚假唤醒

注意:

wait 处于阻塞中:
notify_one 会触发 让wait 解阻塞, 并绑定锁.
wait 非阻塞中:
notify_one 将会是一个虚假唤醒, 这会生产者和消费者 会竞争加锁, 看谁能抢到锁, 也有可能消费者处于比较耗时的步骤4, 那么消费者会抢到锁,继续产生数据

消费者

步骤1>: 获得锁
步骤2>

第二个参数是一个函数, 会进行判断, 当条件成立, 返回true, 继续持有锁.
如果条件不成立,返回false, : 会解锁, 阻塞中. 这会发生的事将是:
1:生产者和消费者抢锁, 生产者抢到,调用notify_one这会就会对wait 产生作用, wait 不再阻塞,并加锁,往下执行
2: 生产者和消费者抢锁, 消费者抢到, 又一次判断条件是否成立,重复上面的步骤.

步骤3>:

当执行到这, 进行耗时的操作的时候, 那就意味着,生产者基本每次都会获得锁, 因为这边好比延迟. 执行不到std::unique_lock std::mutex lock(mutex) 无法与生产者抢锁. 继而生产者每次执行完后调用notify_one 是对 消费者的wait 是无用的(虚假唤醒) ,这就有一个问题, 如下

注意:

这个问题就是, 生产者里面的数据很多, 消费者消费的很慢. 并不是理论上 生产一个,消费一个 有些人会说用notify_all 多开启几个消费者, 加锁解锁是线性的, 同时只有一个抢到,其实没有用的. 至于怎末解决可根据自己的情况这里不再细讲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章