生产者与消费者C++

生产者与消费者C++

生产者与消费者模型

假设有两个线程A、B和一个固定大小的缓冲区,A线程生产数据放入缓冲区,B线程从缓冲区中取出数据进行操作,这就是一个简单的生产者-消费者模型。这里线程A相当于生产者,线程B相当于消费者。

主要实现的功能是:

  • 生产者生产内容
  • 消费者消费内容
  • 固定大小的缓冲区,超过缓冲区大小,生产者停止生产,反之一直等待
  • 缓冲区有内容时消费者进行消费,缓冲区为空时等待
  • 加锁,保证线程安全

生产者与消费者模型的优点

  • 解耦合:将生产者类和消费者类进行解耦,消除代码之间的依赖性,简化工作负载的管理。

  • 复用:对生产者类和消费者类进行独立的复用与扩展。

  • 调整并发数:由于生产者和消费者的处理速度是不一样的,可以调整并发数,来提高任务的处理速度。

  • 异步:生产者只需要关心缓冲区是否还有数据,不需要等待消费者处理完;同样的对于消费者来说,也只需要关注缓冲区的内容,不需要关注生产者,将一个耗时的流程拆成生产和消费两个阶段,通过异步的方式支持高并发。

  • 支持分布式:生产者和消费者通过队列进行通讯,不需要运行在同一台机器上。

代码实现生产者与消费者模型

#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <condition_variable>    
#include <queue>

using namespace std;

queue<int> m_queue;   
condition_variable m_cv;   //条件变量
mutex tex;

void producer()
{
	for (int i = 0; i < 10; i++)
	{
		{
			unique_lock<mutex> lock(tex);
			m_queue.push(i);        //生产者将数据放入队列中
			m_cv.notify_one();      //告诉消费者取数据
			cout << "producer: " << i << endl;
		}
		this_thread::sleep_for(chrono::microseconds(100));
	}
}

void customer()
{
	while (1)
	{

		unique_lock<mutex> lock(tex);
		m_cv.wait(lock, []() {       //wait的第二个参数为true时解除阻塞,为false时阻塞当前线程
			return !m_queue.empty(); //当队列不为空(即有数据)时,lambda表达式返回true
			});
		int value = m_queue.front();   
		m_queue.pop();
		cout << "customer: " << value << endl;
	}
}

int main()
{
	thread t1(producer);   //创建生产者线程
	thread t2(customer);   //创建消费者线程
	t1.join();
	t2.join();
	return 0;
}

条件变量condition_variable

当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

void wait (unique_lock<mutex>& lck);

void wait (unique_lock<mutex>& lck, Predicate pred);

std::condition_variable 提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞,直到另一个线程调用 notify_one或notify_all 唤醒了当前线程,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。
在第二种情况下,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。

在上述代码中,使用的时第二种wait方法。消费者线程调用了condition_variable的wait方法,线程被阻塞,在生产者线程中调用了condition_variable的notify_one方法,唤醒消费者线程。

unique_lock

unique_lock和lock_guard基本用法相同,构造时默认加锁,析构时默认解锁,但unique_lock有个好处就是可以手动解锁。unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。

unique_lock的第二个参数
unique_lock(_Mutex& _Mtx, adopt_lock_t)
  • std::adopt_lock:表示_Mtx这个互斥量已经被lock了(必须要把互斥量提前lock了 ,否者会报异常);
unique_lock(_Mutex& _Mtx, defer_lock_t)
  • 使用给定的互斥量 _Mtx 进行初始化,但不对该互斥量进行加锁操作。
unique_lock(_Mutex& _Mtx, try_to_lock_t)
  • 使用给定的互斥量 _Mtx 进行初始化,并尝试对该互斥量进行加锁操作。如果加锁失败,则创建的 std::unique_lock 对象不与任何互斥量关联。
unique_lock成员函数
  • lock()
  • unlock()
  • try_lock():尝试给互斥量加锁,如果拿不到锁,返回false,如果拿到了锁,返回true,这个函数是不阻塞的;
  • release():返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。
  • try_lock_for:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
  • try_lock_until:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。

为什么有时候需要unlock();锁住的代码段越少,执行越快,整个程序运行效率越高。一般我们将锁住的代码多少成为锁的粒度,粒度一般用粗细来描述;

  • 锁住的代码少,这个粒度叫细,执行效率高;

  • 锁住的代码多,这个粒度叫粗,执行效率低;

要学会尽量选择合适粒度的代码进行保护,粒度太细,可能漏掉共享数据的保护,粒度太粗,影响效率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值