The so-called producer consumer queue is the queue that a thread reads and a thread writes
What we have implemented here is an unlocked producer consumer queue. The key points of the algorithm are as follows:
1.pop only modifies the head node, and push only modifies the tail node
2. Ensure that head and tail are not empty, because once head and tail are empty, you must rewrite head and tail at the same time when you push
Special note:
1. Theoretically, only one thread is allowed to read and one thread is allowed to write
2. Multiple read threads can be allowed to apply for reading, but only one real read is allowed at each time. Similarly, multiple write threads can be allowed to apply for writing, but only one write is allowed at each time. For specific users, please refer to the following implementation
Analysis description:
- Change new NODE to member memory pool to allocate memory. Read and write will be very trivial. Instead, allocate memory in the memory pool to avoid a lot of memory fragmentation and improve efficiency.
Compare flip queue
Flip queue: more suitable for one producer (without outer lock) and multiple consumers, or one consumer (without outer lock) and multiple producers
First and last reading and writing: more suitable for multiple producers and multiple consumers
Flip queue https://mp.csdn.net/posted/103961636
/******************************************************************* ** Filename: producer · consumer · queue. H ** Description: ** Application: ********************************************************************/ #pragma once #include "Trace.h" #include "Common.h" #include "MemPool.h" ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** The so-called producer consumer queue is the queue that a thread reads and a thread writes What we have implemented here is an unlocked producer consumer queue. The key points of the algorithm are as follows: 1.pop Modify only the head node and push only the tail node 2.Ensure that head and tail are not empty, because once head and tail are empty, you must rewrite head and tail at the same time when you push Special note: 1.Theoretically, only one thread is allowed to read and one thread is allowed to write 2.Multiple read threads can be allowed to apply for reading, but only one real read is allowed at each time. Similarly, multiple write threads can be allowed to apply for writing, but only one write is allowed at each time. For specific users, please refer to the following implementation */ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template <typename _DATA> class producer_consumer_queue { struct NODE { _DATA data; NODE * next; }; NODE * m_pHead; NODE * m_pTail; volatile unsigned long m_nPushCount; volatile unsigned long m_nPopCount; tstring m_strName; // Name, used to debug the display owner public: producer_consumer_queue() : m_nPushCount(0), m_nPopCount(0) { //m_pHead = new NODE; m_pHead = (NODE*)MT_Allocator::getInstance().allocate(sizeof(NODE)); assert(m_pHead); // Ensure that Head and Tail are not empty m_pHead->next = 0; m_pTail = m_pHead; }; virtual ~producer_consumer_queue() { _DATA temp_data; while (pop(temp_data)) {} if (m_pHead) { delete m_pHead; m_pHead = 0; m_pTail = 0; } } // Only one thread push is allowed at the same time void push(_DATA & data) { //NODE * pNode = new NODE; NODE * pNode = (NODE*)MT_Allocator::getInstance().allocate(sizeof(NODE)); pNode->data = data; pNode->next = 0; m_pTail->next = pNode; m_pTail = pNode; ++m_nPushCount; static DWORD s_dwTick = 0; if (m_nPushCount - m_nPopCount > 1024 && GetTickCount() - s_dwTick>10000) { s_dwTick = GetTickCount(); DWORD dwPackCounts = m_nPushCount - m_nPopCount; if (dwPackCounts>100000) { WarningLn(0, "[" << m_strName.c_str() << "]Queue busy, Pressing stack" << m_nPushCount << "second, Stack out" << m_nPopCount << "second, Stack length" << dwPackCounts << ", Data length" << sizeof(_DATA) << "byte"); } else { WarningLn(0, "[" << m_strName.c_str() << "]Queue busy, Pressing stack" << m_nPushCount << "second, Stack out" << m_nPopCount << "second, Stack length" << dwPackCounts << ", Data length" << sizeof(_DATA) << "byte"); } } } // Only one thread pop is allowed at the same time bool pop(_DATA & data) { if (m_pHead->next != 0) { ++m_nPopCount; NODE * pNode = m_pHead; m_pHead = m_pHead->next; data = m_pHead->data; //delete pNode; MT_Allocator::getInstance().deallocate(pNode); return true; } return false; } unsigned long count() { return m_nPushCount - m_nPopCount; } public: bool setName(const tchar * pstrName) { if (pstrName == nullptr) { return false; } m_strName = pstrName; return true; } const tchar * getName() { return m_strName.c_str(); } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Producer consumer queue with read-write rights control That is to say, multiple read threads are allowed to apply for reading, but they need to obtain read rights before reading, and multiple write threads are also allowed to apply for writing, but they need to obtain write rights before writing Usage: 1.Call push and pop ex directly 2.Call the acquire? Read? Permission and pop functions, but pay attention to the thread exclusive logic */ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template <typename _DATA> class producer_consumer_queue_ex : public producer_consumer_queue<_DATA> { volatile LONG m_nReadBlock; // Read mark volatile LONG m_nWriteBlock; // Writing signs public: producer_consumer_queue_ex() : producer_consumer_queue(), m_nReadBlock(0), m_nWriteBlock(0) { } // Writing with rights bool push_ex(_DATA & data) { AtomLock __lock__(&m_nWriteBlock); producer_consumer_queue::push(data); return true; } // Reading with rights bool pop_ex(_DATA & data) { if (count() == 0) // Add a judgment to prevent too many calls to atom operation fearless lock memory bus { return false; } AtomLock __lock__(&m_nReadBlock); return producer_consumer_queue::pop(data); } };
Test:
#include "stdafx.h" #include <iostream> #include <WinSock2.h> #include <windows.h> #include "Thread.h" #include "WorkerThreadPool.h" #include "producer_consumer_queue.h" #include "Api.h" using namespace std; producer_consumer_queue_ex<size_t> lp; //Producer class RunnableAdd : public IRunnable { public: void run() { for (size_t i = 0; i < 10000; i++) { lp.push_ex(i); } }; void release(){}; }; //Consumer class RunnableGet : public IRunnable { public: void run() { size_t num = 0; for (size_t i = 0; i < 10000; i++) { lp.pop_ex(num); } }; void release(){}; }; void Fun() { const int gThreadId = 2; HANDLE hd[gThreadId]; Thread *pThread = nullptr; for (size_t i = 0; i < 10; i++) { size_t num = 0; while (lp.pop_ex(num)) { } for (size_t i = 0; i < 10000; i++) { lp.push_ex(i); // Add data first to ensure that every time you access data } pThread = WorkerThreadPool::getInstancePtr()->add(new RunnableAdd); if (pThread == nullptr) { return; } hd[0] = pThread->GetHandle(); pThread = WorkerThreadPool::getInstancePtr()->add(new RunnableGet); if (pThread == nullptr) { return; } hd[1] = pThread->GetHandle(); DWORD result = ::WaitForMultipleObjects(gThreadId, hd, true, INFINITE); cout << "result: " << result << "count: " << lp.count() << endl; } cout << "End" << endl; } int _tmain(int argc, _TCHAR* argv[]) { new WorkerThreadPool; CreateReactor(); Fun(); system("pause"); return 0; }
Result: