基于生产者、消费者的循环队列

-------------------------------------------------------------------------------------------------------------------------

2021-06-23更新

基于生产者消费者模型,在多个消费者等待的过程中,notify会导致惊群效应,解决方案

producer -> consumerMananger 模型(单线程-单线程(管理多线程))

producer->通知consumerMananger去处理

consumerMananger->根据自定义机制(或者空闲状态),让单一的consumer去消费,此过程放在单线程中处理)

------------------------------------------------------------------------------------------------------------------------

0、前言

     生产者消费者模式:一个producer线程生产,一个consumer消耗,当producer过快的时候实行丢帧操作,当consumer过快时实行超时机制的等待(等待不到就返回空)

     通俗的话讲:当"面包"生产过剩的时候,就倒掉仓库中的部分面包(俗称:资本主义倒牛奶),当"面包"消耗的过快的时候,就让顾客等着,如果顾客没有耐心等到(超时机制),就让他回去

 

1、实现自定义事件(事件同步)

#pragma once
#include <windows.h>
#include <process.h>
#include <string>

typedef enum
{
	EVENT_STATUS_FAIL = -1,		// failed
	EVENT_STATUS_SUCCESS = 0,	// success
	EVENT_STATUS_TRUE = 1,		// result was true
	EVENT_STATUS_TIMEOUT = -3,	// time out
	EVENT_STATUS_OPEN = -7,		// Something failed to open
}EVENT_STATUS;


LPCWSTR stringToLpcwstr(const std::string& orig)
{
	int len;
	int slength = orig.length() + 1;
	len = MultiByteToWideChar(CP_ACP, 0, orig.c_str(), slength, 0, 0);
	wchar_t * buf = new wchar_t[len];

	MultiByteToWideChar(CP_ACP, 0, orig.c_str(), slength, buf, len);
	std::wstring r(buf);

	delete[] buf;
	buf = nullptr;

	return r.c_str();
}

class CircularEvent
{
public:
	CircularEvent(bool manualReset = true, const std::string& name = "")
		: mSignaled(true)
		, mName(stringToLpcwstr(name))
	{
		Init(manualReset);
	}
	~CircularEvent()
	{
		Relese();
	}

public:
	virtual EVENT_STATUS Signal()
	{
		if (!mEvent)
		{
			return EVENT_STATUS_OPEN;
		}

		if (SetEvent(mEvent))
		{
			mSignaled = true;
			return EVENT_STATUS_SUCCESS;
		}
		return EVENT_STATUS_FAIL;
	}
	virtual EVENT_STATUS Clear()
	{
		if (!mEvent)
		{
			return EVENT_STATUS_OPEN;
		}

		if (ResetEvent(mEvent))
		{
			mSignaled = false;
			return EVENT_STATUS_SUCCESS;
		}
		return EVENT_STATUS_FAIL;
	}
	virtual EVENT_STATUS SetState(bool signaled = true)
	{
		if (signaled)
		{
			return Signal();
		}

		return Clear();
	}
	virtual EVENT_STATUS GetState(bool* pSignaled)
	{
		if (!mEvent)
		{
			return EVENT_STATUS_OPEN;
		}

		DWORD word = WaitForSingleObject(mEvent, 0);
		if (word == WAIT_TIMEOUT)
		{
			return EVENT_STATUS_FAIL;
		}
		else if (word == WAIT_OBJECT_0)
		{
			*pSignaled = true;
		}
		else
		{
			*pSignaled = false;
		}

		return EVENT_STATUS_SUCCESS;
	}
	virtual EVENT_STATUS SetManualReset(bool manualReset)
	{
		Relese();
		Init(manualReset);
		if (mEvent)
		{
			return EVENT_STATUS_SUCCESS;
		}
		return EVENT_STATUS_FAIL;
	}
	virtual EVENT_STATUS WaitForSignal(uint32_t timeout = 0xffffffff)
	{
		if (!mEvent)
		{
			return EVENT_STATUS_OPEN;
		}

		DWORD word = WaitForSingleObject(mEvent, timeout);

		if (word == WAIT_TIMEOUT)
		{
			return EVENT_STATUS_TIMEOUT;
		}
		else if (word == WAIT_OBJECT_0)
		{
			return EVENT_STATUS_SUCCESS;
		}
		return EVENT_STATUS_FAIL;
	}

private:
	void Init(bool manualReset)
	{
		mEvent = CreateEvent(nullptr, manualReset, mSignaled, mName);
	}
	void Relese()
	{
		if (mEvent)
		{
			CloseHandle(mEvent);
			mEvent = nullptr;
		}
	}

private:
	HANDLE				mEvent;
	LPCWSTR				mName;
	bool				mSignaled;
};

2、实现多线程同步的circularBuffer

#pragma once

#include <shared_mutex>

template <typename DatePtr>
class CircularBuffer
{
public:
	CircularBuffer()
		: mHead(0)
		, mTail(0)
		, mCircBufferCount(0)
		, mFillIndex(0)
		, mEmptyIndex(0)
		, mAbortFlag(nullptr)
	{

	}
	virtual ~CircularBuffer()
	{
		Clear();
	}

	inline void SetAbortFlag(const bool * pAbortFlag)
	{
		mAbortFlag = pAbortFlag;
	}

	inline unsigned int GetCircBufferCount(void) const
	{
		return mCircBufferCount;
	}

	inline bool IsEmpty(void) const
	{
		return GetCircBufferCount() == 0;
	}

	inline unsigned int GetNumFrames(void) const
	{
		return (unsigned int)mDates.size();
	}

	bool Add(DatePtr pInFrameData)
	{
		mDates.push_back(pInFrameData);
		std::shared_mutex* lock = new std::shared_mutex;
		mLocks.push_back(lock);

		return (mDates.size() == mLocks.size() && lock);
	}

	DatePtr StartProduceNextBuffer(void)
	{
		while (1)
		{
			if (!WaitForLockOrAbort(&mDataBufferLock))
				return NULL;

			if (mCircBufferCount == mDates.size())
			{
				mDataBufferLock.Unlock();
				if (!WaitForEventOrAbort(&mNotFullEvent))
					return NULL;

				continue;
			}
			break;
		}
		if (!WaitForLockOrAbort(mLocks[mHead])) return NULL;
		mFillIndex = mHead;
		mHead = (mHead + 1) % ((unsigned int)(mDates.size()));
		mCircBufferCount++;
		if (mCircBufferCount == mDates.size())
			mNotFullEvent.SetState(false);

		mDataBufferLock.Unlock();

		return mDates[mFillIndex];
	}

	void EndProduceNextBuffer()
	{
		mLocks[mFillIndex]->Unlock();
		mNotEmptyEvent.SetState(true);
	}

	DatePtr StartConsumeNextBuffer(void)
	{
		while (1)
		{
			if (!WaitForLockOrAbort(&mDataBufferLock))
				return NULL;

			if (mCircBufferCount == 0)
			{
				mDataBufferLock.Unlock();
				if (!WaitForEventOrAbort(&mNotEmptyEvent))
					return NULL;

				continue;
			}
			break;
		}

		if (!WaitForLockOrAbort(mLocks[mTail]))
			return NULL;

		mEmptyIndex = mTail;
		mTail = (mTail + 1) % ((unsigned int)mDates.size());
		mCircBufferCount--;
		if (mCircBufferCount == 0)
			mNotEmptyEvent.SetState(false);
		mDataBufferLock.Unlock();

		return mDates[mEmptyIndex];
	}

	void EndConsumeNextBuffer()
	{
		mLocks[mEmptyIndex]->Unlock();
		mNotFullEvent.SetState(true);
	}

	void Clear(void)
	{
		for (std::vector<std::shared_mutex*>::iterator iter(mLocks.begin()); iter != mLocks.end(); ++iter)
			delete *iter;

		mLocks.clear();
		mDates.clear();

		mHead = mTail = mFillIndex = mEmptyIndex = mCircBufferCount = 0;
		mAbortFlag = NULL;
	}

private:
	bool WaitForEventOrAbort(CircularEvent *event)
	{
		do
		{
			int status = event->WaitForSignal(timeout);
			if (status == EVENT_STATUS_TIMEOUT)
			{
				if (mAbortFlag)
				{
					if (*mAbortFlag)
					{
						return false;
					}
				}
			}
			if (status == EVENT_STATUS_FAIL)
			{
				return false;
			}
			if (status == EVENT_STATUS_SUCCESS)
			{
				break;
			}
		} while (true);

		return true;
	}
	bool WaitForLockOrAbort(std::shared_mutex * mutex)
	{
		do
		{
			bool status = mutex->try_lock();
			if (status)
			{
				break;
			}
			else
			{
				if (mAbortFlag)
				{
					if (*mAbortFlag)
					{
						return false;
					}
				}
			}
		} 
		while (true);

		return true;
	}

private:
	std::vector <std::shared_mutex*>			mLocks;
	std::vector <DatePtr>						mDates;

	unsigned int								mHead;					// 首指针
	unsigned int								mTail;					// 尾指针
	unsigned int								mCircBufferCount;		// buffer 个数

	CircularEvent								mNotEmptyEvent;			// 空到不空
	CircularEvent								mNotFullEvent;			// 饱和到不饱和
	std::shared_mutex						    mDataBufferLock;

	unsigned int								mFillIndex;				// 添加帧的顺序
	unsigned int								mEmptyIndex;			// 删除帧的index

	const bool *								mAbortFlag;				// 指定中断线程


};

4、丢弃操作和放入操作由外部线程调用(用户根据自定义完成)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个环形缓冲区是基于http://circularbuffer.codeplex.com/ 、 http://en.wikipedia.org/wiki/Circular_buffer 修改的 最近自己项目用到的一个缓冲区,理论上支持多线程在自己的多线程项目测试过,暂时没有问题下面科普下环形缓冲区在内存里的变化: 环形缓冲区首先从空开始并具有设置的长度;在下图中,是一个7字节的缓冲区: 假设在环形缓冲区的中心写入1(确切的起始位置在环形缓冲区中并不重要): 然后,假设将另外两个字节(23)添加到环形缓冲区,它们将放在1之后: 如果删除了两个字节,则环形缓冲区内部的两个最早加入的值将被删除。 环形缓冲区使用FIFO(先进先出)逻辑。 在示例1和2中,第一个进入“环形缓冲区”则第一个被移除,而将3留在缓冲区中。 如果缓冲区有7个字节,则它已经完全占满: 环形缓冲区的一个特性是,当缓冲区已满并执行后续写入操作时,它将开始覆盖最早的数据。 在当前示例中,添加了两个元素A和B并覆盖 了3和4: 最后,如果现在删除了两个字节,则返回的不是3&4而是5&6,因为A&B覆盖了3&4,产生了带有以下内容的缓冲区: 环形缓冲区使用说明: 环形缓冲区的特性是,在使用环形缓冲区时,不会导致内部数据乱七八糟。 (如果使用了非环形缓冲区,那么在没取一个字节时,就必须对所有字节进行移位。)换句话说,环形缓冲区非常适合作为FIFO(先进先出)缓冲区,而标准缓冲区则适合用作FIFO(先进先出)缓冲区。非环形缓冲区非常适合用作LIFO(后进先出)缓冲区。 对于具有固定最大大小的队列,使用环形缓冲是一种很好的实现策略。如果队列采用最大大小,则环形缓冲区是完全理想的实现;所有队列操作都是固定时间。但是,扩展循环缓冲区需要转移存储器,这是非常耗时和消耗资源的。对于任意扩展的队列,可以首选使用链表方法。 这个是24个字节的环形缓冲区 当写指针即将到达读指针时(由于微处理器没有响应),缓冲区停止记录击键。 在某些计算机上会发出哔声。这个排版真累人- .- 2021/5/16  源码更新: [+] 为了大家方便理解新增了Demo 2021/5/15  源码更新: [!] 修复致命BUG,受影响函数:CircularBufferGetBytes、CircularBufferGetBytesFoIndex、CircularBufferPutToMem、CircularBufferPutBytes 下面为更新后代码:
下面是一个简单的基于 Linux 队列生产者消费者 C 语言实例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include <mqueue.h> #include <errno.h> #include <unistd.h> #define QUEUE_NAME "/my_queue" #define MAX_MESSAGES 10 #define MAX_MSG_SIZE 256 #define MSG_BUFFER_SIZE MAX_MSG_SIZE + 10 int main(int argc, char **argv) { mqd_t mq; struct mq_attr attr; char buffer[MSG_BUFFER_SIZE]; int terminate = 0; // 设置消息队列属性 attr.mq_flags = 0; attr.mq_maxmsg = MAX_MESSAGES; attr.mq_msgsize = MAX_MSG_SIZE; attr.mq_curmsgs = 0; // 创建消息队列 mq = mq_open(QUEUE_NAME, O_CREAT | O_RDONLY, 0644, &attr); if (mq == -1) { perror("mq_open"); exit(1); } // 消费者循环等待消息 while (!terminate) { ssize_t bytes_read; bytes_read = mq_receive(mq, buffer, MSG_BUFFER_SIZE, NULL); if (bytes_read == -1) { perror("mq_receive"); exit(1); } buffer[bytes_read] = '\0'; if (strncmp(buffer, "exit", strlen("exit")) == 0) { terminate = 1; } else { printf("Received message: %s\n", buffer); } } // 关闭消息队列 mq_close(mq); mq_unlink(QUEUE_NAME); return 0; } ``` 生产者代码如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include <mqueue.h> #include <errno.h> #include <unistd.h> #define QUEUE_NAME "/my_queue" #define MAX_MESSAGES 10 #define MAX_MSG_SIZE 256 #define MSG_BUFFER_SIZE MAX_MSG_SIZE + 10 int main(int argc, char **argv) { mqd_t mq; char buffer[MSG_BUFFER_SIZE]; int terminate = 0; // 打开消息队列 mq = mq_open(QUEUE_NAME, O_WRONLY); if (mq == -1) { perror("mq_open"); exit(1); } // 生产者循环发送消息 while (!terminate) { printf("Enter message to send (exit to terminate): "); fgets(buffer, MSG_BUFFER_SIZE, stdin); if (strncmp(buffer, "exit", strlen("exit")) == 0) { terminate = 1; } else { ssize_t bytes_sent; bytes_sent = mq_send(mq, buffer, strlen(buffer) + 1, 0); if (bytes_sent == -1) { perror("mq_send"); exit(1); } } } // 发送终止消息 if (mq_send(mq, "exit", strlen("exit") + 1, 0) == -1) { perror("mq_send"); exit(1); } // 关闭消息队列 mq_close(mq); return 0; } ``` 这个例子中,生产者循环等待用户输入消息并将其发送到消息队列中。消费者循环等待从消息队列中接收消息并将其打印到控制台上。当用户输入 "exit" 时,生产者发送一个终止消息,消费者收到终止消息后退出循环并关闭消息队列

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值