最快线程间数据交换算法,有效避免锁竞争 -- TwoQueues

处理多线程数据共享问题注意的几个要点:

1、锁竞争:尽量减少锁竞争的时间和次数。

2、内存:尽量是使用已分配内存,减少内存分配和释放的次数。尽量是用连续内存,减少共享占用的内存量。



多线程数据交换简单方案A:

定义一个list,再所有操作list的地方进行加锁和解锁。

简单模拟代码:

  1. class CSimpleQueue 
  2. public
  3.     CSimpleQueue() 
  4.     { 
  5.         InitializeCriticalSection(&m_crit); 
  6.     } 
  7.     ~CSimpleQueue() 
  8.     { 
  9.         DeleteCriticalSection(&m_crit); 
  10.     } 
  11.  
  12.     // 添加数据 
  13.     void AddData(DATA pData) 
  14.     { 
  15.         EnterCriticalSection(&m_crit); 
  16.         m_listDATA.push_back(pData); 
  17.         LeaveCriticalSection(&m_crit); 
  18.     } 
  19.     // 获取数据 
  20.     bool GetData(DATA data) 
  21.     { 
  22.         EnterCriticalSection(&m_crit); 
  23.         if (m_listDATA.size() > 0) 
  24.         { 
  25.             data = m_listDATA.front(); 
  26.             m_listDATA.pop_front(); 
  27.             LeaveCriticalSection(&m_crit); 
  28.  
  29.             return true
  30.         } 
  31.         else 
  32.         { 
  33.             LeaveCriticalSection(&m_crit); 
  34.             return false
  35.         } 
  36.     } 
  37.  
  38. private
  39.     list<DATA> m_listDATA; 
  40.     CRITICAL_SECTION m_crit; 
  41. }; 
class CSimpleQueue
{
public:
	CSimpleQueue()
	{
		InitializeCriticalSection(&m_crit);
	}
	~CSimpleQueue()
	{
		DeleteCriticalSection(&m_crit);
	}

	// 添加数据
	void AddData(DATA pData)
	{
		EnterCriticalSection(&m_crit);
		m_listDATA.push_back(pData);
		LeaveCriticalSection(&m_crit);
	}
	// 获取数据
	bool GetData(DATA data)
	{
		EnterCriticalSection(&m_crit);
		if (m_listDATA.size() > 0)
		{
			data = m_listDATA.front();
			m_listDATA.pop_front();
			LeaveCriticalSection(&m_crit);

			return true;
		}
		else
		{
			LeaveCriticalSection(&m_crit);
			return false;
		}
	}

private:
	list<DATA> m_listDATA;
	CRITICAL_SECTION m_crit;
};


多线程数据交换优化方案B -- TwoQueues:

文章最后有TwoQueues的详细实现源代码。


TwoQueues的实现分析:

锁竞争:TwoQueues使用双内存块交换的方式减少锁竞争,数据写入时会进行加锁和解锁操作,但读取数据时,只是当当前读取队列的数据读取完毕时,进行两个队列交换时才进行加锁和解锁操作。可以说,数据的读取是不进行加锁的。这样,最大限度的降低了锁竞争问题。

内存:TwoQueues采用预先分配内存块的方式,初始化TwoQueues时就已经将存放数据的内存分配好了。之后数据存放于已经分配的内存块上。无需再次分配内存,不会再次进行内存分配。

TwoQueues的进一步优化,TwoQueues在存放数据时,完全可以模仿【数据长度+数据】的方式存放数据。但是这种方式会增加数据存在性检测的效率。TwoQueues则采用了一种链表的方式进行存放数据。

链表结构:

  1. typedef struct tagDataHead 
  2.     { 
  3.         tagDataHead() 
  4.         { 
  5.             pHead = NULL; 
  6.             nLen = 0; 
  7.             pNext = NULL; 
  8.         } 
  9.         void ReSet() 
  10.         { 
  11.             pHead = NULL; 
  12.             nLen = 0; 
  13.             pNext = NULL; 
  14.         } 
  15.         char* pHead; 
  16.         unsigned int nLen; 
  17.         tagDataHead* pNext; 
  18.     }DATAHEAD; 
typedef struct tagDataHead
	{
		tagDataHead()
		{
			pHead = NULL;
			nLen = 0;
			pNext = NULL;
		}
		void ReSet()
		{
			pHead = NULL;
			nLen = 0;
			pNext = NULL;
		}
		char* pHead;
		unsigned int nLen;
		tagDataHead* pNext;
	}DATAHEAD;

此链表存放了数据头指针和数据长度。当TwoQueues中存在数据时,可以直接通过链表节点拿数据。因为链表每次创建时也是需要进行申请内存,而内存申请是一个比较耗效率的事情,TwoQueues再此做了一个小小的处理,当链表头不存在时,会进行ReAlloc调用,一性申请MALLOC_SIZE个链表头结构。并且链表从链表上解下时并不会释放内存而是放入一个pFreeDataHead链表上,当需要申请链表头结构时,会先从pFreeDataHead链表上取链表结构。此处理减少了内存分配和释放的次数,提高了多线程数据共享的效率。

以上的各种优化处理,使TwoQueues的效率得到了极大的提升。

之前做过的内存共享的测试。A线程不停的写入数据,B线程不停的读取数据。

 debug版本release版本
TwoQueues80-100万次/秒170-180万次/秒
CSimpleQueues20万次/秒5万次/秒


双队列的使用:

创建对象:

CTwoQueues m_cTwoQueues;

// 初始化双队列大小,因为是双队列,所以内存占用量是两份。

m_cTwoQueues.Init(0x0FFFFFFF);


写入方式:

m_cTwoQueues.PushData(sz, strlen(sz)+1);


读取方式:

const void* pData = NULL;
unsigned int nLen = 0;

if (m_cTwoQueues.PrepareData(pData, nLen))
{
// 处理数据 ...

// 确认(丢弃)此数据
m_cTwoQueues.ConfimData();
}


// 下面是TwoQueues的所有源代码  源代码下载

  1. #pragma once 
  2. #include <assert.h> 
  3.  
  4. namespace clwCore 
  5. #define MALLOC_SIZE 128 
  6.  
  7.     typedef struct tagDataHead 
  8.     { 
  9.         tagDataHead() 
  10.         { 
  11.             pHead = NULL; 
  12.             nLen = 0; 
  13.             pNext = NULL; 
  14.         } 
  15.         void ReSet() 
  16.         { 
  17.             pHead = NULL; 
  18.             nLen = 0; 
  19.             pNext = NULL; 
  20.         } 
  21.         char* pHead; 
  22.         unsigned int nLen; 
  23.         tagDataHead* pNext; 
  24.     }DATAHEAD; 
  25.  
  26.     typedef struct tagDataMem 
  27.     { 
  28.         tagDataMem(unsigned int nSize) 
  29.         { 
  30.             if (0 == nSize) 
  31.             { 
  32.                 assert(false); 
  33.                 return
  34.             } 
  35.  
  36.             nMaxSize = nSize; 
  37.             pDataMem = (char*)malloc(nSize*sizeof(char)); 
  38.             nDataPos = 0; 
  39.  
  40.             if (NULL == pDataMem) 
  41.             { 
  42.                 // CTwoQueues申请malloc内存失败。 
  43.                 assert(false); 
  44.             } 
  45.  
  46.             pListDataHead = NULL; 
  47.             pCurDataHead = NULL; 
  48.             pFreeDataHead = NULL; 
  49.         } 
  50.         ~tagDataMem() 
  51.         { 
  52.             // 释放节点 
  53.             ReSet(); 
  54.             while(NULL != pFreeDataHead) 
  55.             { 
  56.                 DATAHEAD* pTempDataHead = pFreeDataHead; 
  57.                 pFreeDataHead = pFreeDataHead->pNext; 
  58.  
  59.                 delete pTempDataHead; 
  60.                 pTempDataHead = NULL; 
  61.             } 
  62.  
  63.             free(pDataMem); 
  64.             pDataMem = NULL; 
  65.             nDataPos = 0; 
  66.         } 
  67.         bool ReAlloc() 
  68.         { 
  69.             for (unsigned short i=0; i<MALLOC_SIZE; ++i) 
  70.             { 
  71.                 DATAHEAD* pTempDataHead = new DATAHEAD; 
  72.                 //pTempDataHead->ReSet();    //构造时已经初始化 
  73.  
  74.                 if (NULL == pTempDataHead) 
  75.                 { 
  76.                     // 申请DATAHEAD内存失败。 
  77.                     assert(false); 
  78.                     return false
  79.                 } 
  80.  
  81.                 pTempDataHead->pNext = pFreeDataHead; 
  82.                 pFreeDataHead = pTempDataHead; 
  83.             } 
  84.  
  85.             return true
  86.         } 
  87.         DATAHEAD* GetDataHead() 
  88.         { 
  89.             if (NULL != pFreeDataHead) 
  90.             { 
  91.                 DATAHEAD* pTempDataHead = pFreeDataHead; 
  92.                 pFreeDataHead = pFreeDataHead->pNext; 
  93.  
  94.                 return pTempDataHead; 
  95.             } 
  96.  
  97.             if (ReAlloc()) 
  98.             { 
  99.                 if (NULL != pFreeDataHead) 
  100.                 { 
  101.                     DATAHEAD* pTempDataHead = pFreeDataHead; 
  102.                     pFreeDataHead = pFreeDataHead->pNext; 
  103.  
  104.                     return pTempDataHead; 
  105.                 } 
  106.             } 
  107.  
  108.             // ASSERT("GetDataHead返回NULL。"); 
  109.             assert(false); 
  110.             return NULL; 
  111.         } 
  112.         unsigned int GetFreeLen()   //空闲内存长度 
  113.         { 
  114.             return nMaxSize-nDataPos; 
  115.         } 
  116.         bool PushData(void* pData, unsigned int nLen) 
  117.         { 
  118.             if (nDataPos+nLen >= nMaxSize) 
  119.             { 
  120.                 return false
  121.             } 
  122.  
  123.             DATAHEAD* pTempDataHead = GetDataHead(); 
  124.  
  125.             if (NULL == pTempDataHead) 
  126.             { 
  127.                 return false
  128.             } 
  129.  
  130.             // 构造数据头结构 
  131.             pTempDataHead->pHead = (pDataMem+nDataPos); 
  132.             pTempDataHead->nLen = nLen; 
  133.             pTempDataHead->pNext = NULL; 
  134.  
  135.             // 拷贝数据 
  136.             memcpy(pDataMem+nDataPos, pData, nLen); 
  137.             nDataPos += nLen; 
  138.  
  139.             if (NULL == pListDataHead) 
  140.             { 
  141.                 pListDataHead = pTempDataHead; 
  142.                 pCurDataHead = pTempDataHead; 
  143.                 return true
  144.             } 
  145.             else 
  146.             { 
  147.                 pCurDataHead->pNext = pTempDataHead; 
  148.                 pCurDataHead = pCurDataHead->pNext; 
  149.                 return true
  150.             } 
  151.         } 
  152.  
  153.         bool IsEmpty()  //判断是否有数据 
  154.         { 
  155.             return (NULL==pListDataHead)?true:false
  156.         } 
  157.  
  158.         bool PrepareData(const void*& pData, unsigned int& nLen)    //准备一条数据 
  159.         { 
  160.             if (NULL != pListDataHead) 
  161.             { 
  162.                 pData = pListDataHead->pHead; 
  163.                 nLen = pListDataHead->nLen; 
  164.                 return true
  165.             } 
  166.             else 
  167.             { 
  168.                 return false
  169.             } 
  170.         } 
  171.         void ConfimData()   //删除一条数据 
  172.         { 
  173.             if (NULL == pListDataHead) 
  174.             { 
  175.                 return
  176.             } 
  177.  
  178.             DATAHEAD* pTempDataHead = pListDataHead; 
  179.             pListDataHead = pListDataHead->pNext; 
  180.  
  181.             pTempDataHead->ReSet(); 
  182.             pTempDataHead->pNext = pFreeDataHead; 
  183.             pFreeDataHead = pTempDataHead; 
  184.         } 
  185.         void ReSet()    //重置内存存储对象 
  186.         { 
  187.             while(NULL != pListDataHead) 
  188.             { 
  189.                 DATAHEAD* pTempDataHead = pListDataHead; 
  190.                 pListDataHead = pListDataHead->pNext; 
  191.  
  192.                 pTempDataHead->ReSet(); 
  193.                 pTempDataHead->pNext = pFreeDataHead; 
  194.                 pFreeDataHead = pTempDataHead; 
  195.             } 
  196.  
  197.             nDataPos = 0; 
  198.             pCurDataHead = NULL; 
  199.         } 
  200.  
  201.         char* pDataMem;         //数据内存区域 
  202.         unsigned int nDataPos;  //数据存储位置 
  203.         unsigned int nMaxSize;  //最大存储区域大小 
  204.  
  205.         DATAHEAD* pListDataHead; 
  206.         DATAHEAD* pCurDataHead; 
  207.         DATAHEAD* pFreeDataHead;    //空闲头结构队列 
  208.     }DATAMEM; 
  209.  
  210.     class CTwoQueues 
  211.     { 
  212.     public
  213.         CTwoQueues(void
  214.         { 
  215.             InitializeCriticalSection(&m_crit); 
  216.             m_pDataMemPush = NULL; 
  217.             m_pDataMemPop = NULL; 
  218.         } 
  219.         ~CTwoQueues(void
  220.         { 
  221.             if (NULL != m_pDataMemPush) 
  222.             { 
  223.                 delete m_pDataMemPush; 
  224.                 m_pDataMemPush = NULL; 
  225.             } 
  226.  
  227.             if (NULL != m_pDataMemPop) 
  228.             { 
  229.                 delete m_pDataMemPop; 
  230.                 m_pDataMemPop = NULL; 
  231.             } 
  232.             DeleteCriticalSection(&m_crit); 
  233.         } 
  234.  
  235.     public
  236.         void Init(unsigned int nSize) 
  237.         { 
  238.             if (0 == nSize) 
  239.             { 
  240.                 // 初始化CTwoQueues对象失败。 
  241.                 assert(false); 
  242.                 return
  243.             } 
  244.  
  245.             m_pDataMemPush = new DATAMEM(nSize); 
  246.             m_pDataMemPop = new DATAMEM(nSize); 
  247.         } 
  248.  
  249.         bool PushData(void* pData, unsigned int nLen) 
  250.         { 
  251.             //unsigned int nFreeLen = m_pDataMemPush->GetFreeLen(); 
  252.  
  253.             bool bResult = false
  254.  
  255.             EnterCriticalSection(&m_crit); 
  256.             bResult = m_pDataMemPush->PushData(pData, nLen); 
  257.             LeaveCriticalSection(&m_crit); 
  258.  
  259.             return bResult; 
  260.         } 
  261.         bool PrepareData(const void*& pData, unsigned int& nLen) 
  262.         { 
  263.             bool bCanRead = true
  264.             if (m_pDataMemPop->IsEmpty()) 
  265.             { 
  266.                 // 队列没有数据了 
  267.                 EnterCriticalSection(&m_crit); 
  268.                 if (m_pDataMemPush->IsEmpty()) 
  269.                 { 
  270.                     // Push队列为空 
  271.                     LeaveCriticalSection(&m_crit); 
  272.                     bCanRead = false
  273.                 } 
  274.                 else 
  275.                 { 
  276.                     m_pDataMemPop->ReSet();  //充值读取队列 
  277.                     DATAMEM* pTempDataMem = m_pDataMemPush; 
  278.                     m_pDataMemPush = m_pDataMemPop; 
  279.                     m_pDataMemPop = pTempDataMem; 
  280.                     LeaveCriticalSection(&m_crit); 
  281.                     bCanRead = true
  282.                 } 
  283.             } 
  284.  
  285.             if (bCanRead) 
  286.             { 
  287.                 return m_pDataMemPop->PrepareData(pData, nLen); 
  288.             } 
  289.             else 
  290.             { 
  291.                 return false
  292.             } 
  293.         } 
  294.  
  295.         void ConfimData() 
  296.         { 
  297.             m_pDataMemPop->ConfimData(); 
  298.         } 
  299.  
  300.     private
  301.         DATAMEM* m_pDataMemPush; 
  302.         DATAMEM* m_pDataMemPop; 
  303.         CRITICAL_SECTION m_crit; 
  304.  
  305.     }; 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值