C++多线程打工人

18 篇文章 0 订阅
本文介绍了C++中基于互斥锁和条件变量的生产者消费者模型实现,包括共享缓冲区、线程同步以及可能遇到的问题,如饥饿现象。通过实例展示了如何使用条件变量简化等待逻辑,并讨论了应用场景和注意事项。
摘要由CSDN通过智能技术生成

        为啥写这个,今天面试问到了~当时基于信号量写了一个单线程+无锁队列的实现,但是面试官实际想要的是多线程+条件变量实现的方式。

基本概念

        生产者消费者模型是一种常见的并发设计模式,用于处理生产者(生成数据)和消费者(处理数据)之间的协调问题。在多线程环境中,生产者和消费者可能运行在不同的线程中,因此需要同步机制来避免竞态条件和确保数据的一致性。        

        在生产者消费者模型中,生产者负责生成数据并将其放入一个共享的缓冲区,而消费者则从缓冲区中取出数据进行处理。共享缓冲区通常是有限大小的,这意味着生产者在缓冲区满时必须等待,而消费者在缓冲区空时也必须等待。

开搞

        C++中的生产者消费者模型通常依赖于线程库(如C++11标准中的线程库)和同步原语(如互斥锁、条件变量等)来实现。

        为了实现生产者消费者模型,我们需要以下组件:

  • 共享缓冲区:通常是一个队列,用于存储生产者生成的数据。
  • 互斥锁:用于保护共享缓冲区,确保同一时间只有一个线程可以访问缓冲区。
  • 条件变量:用于线程间的同步,允许线程在某个条件不满足时等待,并在条件满足时被唤醒。

以下是一个简单的生产者消费者模型实现示例:

首先是一个生产者消费者队列,提供了Produce和Consume能力,并维护了一个公共队列作为数据存储。

template <typename T>
class ProduceConsumeQueue {
public:
	ProduceConsumeQueue(uint32_t size) : sz_(size) {}

	void Produce(const T& data) {
		std::unique_lock<std::mutex> ulock(lock_);

		produce_cond_.wait(ulock, [this]() {
			return this->queue_.size() < sz_;
		});

		queue_.push(data);
		std::cout << "produce: " << data << std::endl;

		consume_cond_.notify_one();
	}

	T Consume() {
		std::unique_lock<std::mutex> ulock(lock_);

		consume_cond_.wait(ulock, [this]() {
			return !queue_.empty();
		});

		auto data = queue_.front();
		queue_.pop();

		std::cout << "consume: " << data << std::endl;

		produce_cond_.notify_one();
		return data;
	}

private:
	std::mutex lock_;
	std::condition_variable produce_cond_;
	std::condition_variable consume_cond_;

	uint32_t sz_{0};
	std::queue<T> queue_;
};

        基于这个队列提供的生产消费能力,分别实现:

        一个资产阶级:        多个打工人

class Producer {
public:
	Producer() = default;

	void Init(std::shared_ptr<ProduceConsumeQueue<int> >& queue) {
		queue_ = queue;
	}

	void Produce() {
		int i = 0;
		while (true) {
			sleep(1);
			queue_->Produce(i++);
		}
	}

private:
	std::shared_ptr<ProduceConsumeQueue<int> > queue_;
};

class Consumer {
public:
	Consumer() = default;

	void Init(std::shared_ptr<ProduceConsumeQueue<int> >& queue) {
		queue_ = queue;
	}

	void Consume() {
		while (true) {
			auto val = queue_->Consume();
		}
	}

private:
	std::shared_ptr<ProduceConsumeQueue<int> > queue_;
};

int main() {
	auto pc_queue = std::make_shared<ProduceConsumeQueue<int> >(3);

	std::vector<Producer> producers(3);  // 打工人
	std::vector<Consumer> consumers(1);  // 资产阶级

	std::vector<std::thread> pthreads;
	std::vector<std::thread> cthreads;

	for (auto& p : producers) {
		p.Init(pc_queue);
		pthreads.emplace_back(std::thread(&Producer::Produce, &p));
	}

	for (auto& c : consumers) {
		c.Init(pc_queue);
		cthreads.emplace_back(std::thread(&Consumer::Consume, &c));
	}

	// join
	pause();

	return 0;
}

 啪一下很快啊

code % g++ product_consume.cpp -std=c++17
code % ./a.out                           
produce: 0
consume: 0
produce: 0
consume: 0
produce: 0
consume: 0
produce: 1
consume: 1
produce: 1
consume: 1
produce: 1
consume: 1
^C
code % 

        上面的生产者消费者模型是一种简单而有效的并发编程模型,它具有以下优点:易于理解和实现、可以很好地解决多个线程之间的数据共享问题、可以提高程序的性能。但是,也存在一些缺点:可能存在饥饿问题(可以对生产者、消费者排队处理)。

        生产者消费者模型可以应用于各种场景,例如:多线程文件读写、多线程网络通信、多线程数据库访问等。

        上面调用std::condition_variable的wait操作的第二个参数是一个判断条件,收到条件信号的时候会判断是再判断是否满足条件,不满足会循环再等,省得我们手动再写一个循环判断了。
        在设计生产者消费者模型时,还需要考虑如何优雅地终止线程和处理异常情况,如join。

        附完整代码:https://github.com/Fireplusplus/Code_Cpp/blob/master/ProducerConsumer.cpp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fireplusplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值