設計模式:生產者-消費者模式

  • 引:生產者-消費者模式是一個十分經典的多線程併發協作的模式,嚴格地講並不屬於設計模式的範疇,但該程序設計思想同樣爲程序設計提供了很好的思路,所以就加在設計模式版塊了。弄懂生產者-消費者問題能夠讓我們對併發編程的理解加深。所謂生產者-消費者問題,實際上主要是包含了兩類線程,一種是生產者線程用於生產數據,另一種是消費者線程用於消費數據,爲了解耦生產者和消費者的關係,通常會採用共享的數據區域,就像是一個倉庫,生產者生產數據之後直接放置在共享數據區中,並不需要關心消費者的行爲;而消費者只需要從共享數據區中去獲取數據,就不再需要關心生產者的行爲。但是,這個共享數據區域中應該具備這樣的線程間併發協作的功能:
    • 如果共享數據區已滿的話,阻塞生產者繼續生產數據放置入內;
    • 如果共享數據區爲空的話,阻塞消費者繼續消費數據;
  • 概要:
  • 下面通過實例,說明一個簡易的生產者-消費者模式
//sync_queue.h

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

template<typename T>
class SyncQueue
{
public:
	SyncQueue();
	~SyncQueue();

	void put(const T& val);
	void put(T&& val);
	void get(T& val);
private:
	std::condition_variable m_cond;
	std::mutex m_mutex;
	std::list<T> m_q;

};

template<typename T>
SyncQueue<T>::SyncQueue() {}

template<typename T>
SyncQueue<T>::~SyncQueue() {}

template<typename T>
void SyncQueue<T>::get(T& val)
{
	std::unique_lock<std::mutex> lck(m_mutex);
	
	//體會下:下面使用while不使用if是防止虛假喚醒:
	//假設是在等待消費隊列,一個線程A被nodify,
	//但是還沒有獲得鎖時,另一個線程B獲得了鎖,並消費掉了隊列中的數據。
	//B退出或wait後,A獲得了鎖,而這時條件已不滿足。
	//對於下面情況,虛假喚醒後在A線程再次通過while內參數判斷,如果爲空則繼續等待。如果用if則A線程會向下執行,輸出超時(實際不是超時)
	while (m_q.empty())
	{
		if (m_cond.wait_for(lck, std::chrono::milliseconds{ 2000 }) == std::cv_status::timeout)
		{
			break;
		}
	}
	if (m_q.empty())
	{
		throw "超時";
	}
	else
	{
		val = m_q.front();
		m_q.pop_front();
		std::cout << "取出一個元素" << std::endl;
	}
}

template<typename T>
void SyncQueue<T>:: put(const T& val)
{
	{
		std::lock_guard<std::mutex> lck(m_mutex);
		m_q.push_back(val);
	}
	m_cond.notify_one();
}

template<typename T>
void SyncQueue<T>::put(T&& val)
{
	std::lock_guard<std::mutex> lck(m_mutex);
	m_q.push_back(std::forward<T>(val));
	m_cond.notify_one();
}
//main.cpp

#include "sync_queue.h"


SyncQueue<int> g_queue;

void produce()
{
	static int i = 0;
	while (true)
	{
		g_queue.put(++i);
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));

	}
}

void consumer()
{
	while (true)
	{
		try
		{
			int val;
			g_queue.get(val);
			std::cout << "獲取" << val << std::endl;
		}
		catch (const char* e)
		{
			std::cout << e << std::endl;
		}
	}
}

int main()
{
	std::thread t_c{consumer};
	std::thread t_p{ produce };

	t_c.join();
	t_p.join();
	return 0;
}

// 在多個消費者的情況 put 函數還可以進行一些改進 

// if(m_q.size()>n)
// {m_cond.notify_all();}
// else
// {m_cond.notify_one();} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章