[Common c/c++] 生产者消费者模型 using mutex/cv/semaphore

前言:

生产者消费者模型是老生常谈的话题,实现手段也是各种各样,不同的手段的 运行效率也是天壤之别。代码简洁度,数据安全性,运行稳定性,运行性能等等要素很难做到兼顾。

最基本的模型 -> 大粒度锁 + 忙等(循环check / busy check)

组件:

mutex

代码:

#include 
#include 
#include 
#include 
#include 

std::list FIFO;
std::mutex lock;
long consumer_v = -1;
long producer_v = 9999999;

void consumer(){
	static long times=0;
	while(consumer_v!=0){
		std::unique_lock ul(lock);
		if(!FIFO.empty()){
			consumer_v =std::move(FIFO.front());
			FIFO.pop_front();
			times++;
		}else{
            //usleep(1);   //降低轮询次数以节省cpu
			times++;
		}
	}
	printf("consumer times : %ld\n" , times);
}

void producer(){
	static long times=0;
	while(producer_v--!=0){
		std::unique_lock ul(lock);
		FIFO.push_back(producer_v);
		times++;
	}
	printf("producer times : %ld\n" , times);
}

int main()
{
	std::thread cons(consumer);
	std::thread prod(producer);
	cons.join();
	prod.join();
}

以上代码中,cpu通常会达到200%,原因是 consumer 中需要判断FIFO 中是否有数据,如果没有数据要再次加锁和判断,因此这数据 busy check 代码结构,这个过程会非常耗费 cpu 。

通过top命令查看:

  %CPU
 200.0

可以通过usleep来降低轮询频率从而降低cpu ,但是弊端代码就是执行时间会变长。

$ time ./1
producer times : 9999999
consumer times : 10002394

real    0m16.661s
user    0m19.614s
sys     0m13.541s

每次运行上述代码都会发现输出结果中,consumer times 的值会有很大波动,有时比 producer times 大几百,有时大几千,这些就是无用轮询的次数。

优缺点:

优点:代码简洁易懂,方便阅读和修改,逻辑清晰。

缺点:

1)cpu和运行效率无法兼得,要么cpu忙(这往往是绝对无法接收的);

2)要么运行效率无法得到保障(sleep间隔长了则效率低,短了则cpu忙);

3)竞争数据的加锁粒度大,一次性把整个list都锁住了。不过这一点不是太大的问题,而且优化起来难度较高,一般属于无锁编程范畴。不属于严重的缺点。


改善CPU的模型 -> 大粒度锁 + 休眠唤醒

为了改善 cpu 忙等问题,可以使用休眠唤醒机制。把唤醒工作交给内核,达到在不进行 busy check 的前提下,还可以来提升等待线程的响应效率的目的。

组件: 

conditional variable / semaphore

其他:

当我们锁住列表的时候,释放锁的时机要控制好,建议通过 std::move 把需要处理的数据从 FIFO 中拿出来,或者 通过拷贝的方式拷贝拿出来,然后立刻就把锁释放掉,这样不会影响其他线程加锁。不可以在锁住状态中执行耗时操作,除非你有充分的理由或者知道自己在干啥。

你可能感兴趣的:(#,Common/Linux,C/C++,c语言,c++,开发语言)