問題由來,
考慮設計一個內存池類,http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html?ca=drs-cn。
內存池類代碼如下:
.h文件

1 #pragma once 2 3 4 #include <memory> 5 #include <iostream> 6 #include <windows.h> 7 using namespace std; 8 9 10 #define USHORT unsigned int 11 #define ULONG unsigned long 12 #define MEMPOOL_ALIGNMENT 4 13 14 #pragma warning( disable : 4291 ) 15 16 struct MemoryBlock 17 { 18 unsigned short nSize; 19 unsigned short nFree; 20 unsigned short nFirst; 21 //std::shared_ptr<MemoryBlock> pNext; 22 MemoryBlock* pNext; 23 char aData[1]; 24 25 static void* operator new(size_t, unsigned short nTypes, unsigned short nUnitSize) 26 { 27 return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize); 28 } 29 30 static void operator delete(void *p, size_t) 31 { 32 ::operator delete (p); 33 } 34 35 MemoryBlock (unsigned short nTypes = 1, unsigned short nUnitSize = 0); 36 ~MemoryBlock() {} 37 38 }; 39 40 class MemoryPool 41 { 42 private: 43 //std::shared_ptr<MemoryBlock> pBlock; 44 MemoryBlock* pBlock; 45 unsigned short nUnitSize; 46 unsigned short nInitCount; 47 unsigned short nGrowSize; 48 CRITICAL_SECTION allocSection; 49 CRITICAL_SECTION freeSection; 50 51 public: 52 static unsigned char nMemoryIsDeleteFlag; //標記MemoryPool內存是否釋放 53 54 public: 55 MemoryPool( unsigned short nUnitSize, 56 unsigned short nInitCount = 1024, 57 unsigned short nGrowSize = 256 ); 58 ~MemoryPool(); 59 60 void* Alloc(); 61 void Free( void* p ); 62 };
.cpp文件

1 #include "Memory.h" 2 3 4 MemoryBlock::MemoryBlock(unsigned short nTypes /* = 1 */, unsigned short nUnitSize /* = 0 */) 5 : nSize( nTypes * nUnitSize ) 6 , nFree( nTypes - 1 ) 7 , nFirst(1) 8 , pNext(NULL) 9 { 10 char * pData = aData; 11 for (unsigned short i = 1; i < nTypes; i++) 12 { 13 *reinterpret_cast<unsigned short*>(pData) = i; 14 pData += nUnitSize; 15 } 16 } 17 18 //不允許其他地方修改 19 unsigned char MemoryPool::nMemoryIsDeleteFlag = 0; 20 21 // UnitSize值不宜設置為過大值 22 MemoryPool::MemoryPool( unsigned short _nUnitSize, unsigned short _nInitCount , \ 23 unsigned short _nGrowSize ) 24 { 25 if ( _nUnitSize > 4 ) 26 nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); 27 else if ( _nUnitSize <= 2 ) 28 nUnitSize = 2; 29 else 30 nUnitSize = 4; 31 32 nUnitSize = _nUnitSize; 33 nInitCount = _nInitCount; 34 nGrowSize = _nGrowSize; 35 pBlock = NULL; 36 37 InitializeCriticalSection (&allocSection); 38 InitializeCriticalSection (&freeSection); 39 40 } 41 42 43 void * MemoryPool::Alloc() 44 { 45 //內存池沒有MemoryBlock時,申請一塊內存 46 EnterCriticalSection (&allocSection); 47 if ( !pBlock ) 48 { 49 //auto pp = new(nInitCount,nUnitSize) MemoryBlock(nInitCount,nUnitSize); 50 pBlock = new(nInitCount,nUnitSize) MemoryBlock(nInitCount,nUnitSize); 51 LeaveCriticalSection (&allocSection); 52 //返回MemoryBlock中的第一個內存塊的地址 53 return (void*)(pBlock->aData+1); 54 } 55 56 //內存池非空時 57 MemoryBlock* pMyBlock = pBlock; //從鏈表頭部pBlock開始 58 while (pMyBlock && !pMyBlock->nFree ) //搜索可用內存快 59 pMyBlock = pMyBlock->pNext; 60 61 // 最后一個MemoryBlock有空閑的內存塊時 62 if ( pMyBlock ) 63 { 64 //找到第一個空閑內存塊的地址 65 char* pFree = pMyBlock->aData + (pMyBlock->nFirst * nUnitSize); 66 67 //MemoryBlock構造時,*((USHORT*)pFree)的值為所在的內存塊數; 68 //nFirst記錄本MemoryBlock的第一個空閑內存塊的計數; 69 pMyBlock->nFirst = *((USHORT*)pFree); 70 71 pMyBlock->nFree--; 72 73 LeaveCriticalSection(&allocSection); 74 return (void*)pFree; 75 } 76 //所有的MemoryBlock都占滿時 77 else 78 { 79 if ( !nGrowSize ) 80 return NULL; 81 82 pMyBlock = new(nInitCount, nUnitSize) MemoryBlock(nInitCount, nUnitSize); 83 if ( !pMyBlock ) 84 return NULL; 85 86 pMyBlock->pNext = pBlock ; 87 pBlock = pMyBlock ; 88 89 LeaveCriticalSection(&allocSection); 90 return (void*)(pMyBlock->aData); 91 } 92 93 LeaveCriticalSection(&allocSection); 94 return (void*)pBlock->aData; 95 } 96 97 //回收,內存返還給MemoryPool,而不是系統 98 void MemoryPool::Free( void* p ) 99 { 100 EnterCriticalSection (&freeSection); 101 USHORT* pfree_us; 102 USHORT pfree_index, pHead; 103 pfree_us = (USHORT *)p; 104 MemoryBlock* pMyBlock = pBlock; 105 106 if(pMyBlock == NULL) //pFree不是屬於內存池管理的內存 107 { 108 LeaveCriticalSection (&freeSection); 109 return; 110 } 111 112 //定位pFree所在的塊 113 while ( ((ULONG)pMyBlock->aData > (ULONG)pfree_us) || 114 ((ULONG)pfree_us >= ((ULONG)pMyBlock->aData + pMyBlock->nSize*nUnitSize)) ) 115 { 116 pMyBlock = pMyBlock->pNext; 117 } 118 119 if(pMyBlock == NULL) //pFree不是屬於內存池管理的內存 120 { 121 LeaveCriticalSection (&freeSection); 122 return; 123 } 124 125 //回收pFree 126 pMyBlock->nFree++; 127 pHead = pMyBlock->nFirst; //第一個可用索引 128 pfree_index = ( (ULONG)pfree_us-(ULONG)pMyBlock->aData )/nUnitSize; //獲取pFree的索引號 129 pMyBlock->nFirst = pfree_index; //pFree插入到可用塊首 130 *pfree_us = pHead; //之前的塊首鏈入 131 132 //判斷是否需要將Block內存返回給系統 133 if ( (pMyBlock->nFree * nUnitSize) == pMyBlock->nSize ) 134 { 135 pBlock = pMyBlock->pNext; 136 delete pMyBlock; 137 } 138 LeaveCriticalSection (&freeSection); 139 } 140 141 142 MemoryPool::~MemoryPool() 143 { 144 if ( nMemoryIsDeleteFlag == 0 ) 145 { 146 147 MemoryBlock* my_block = pBlock; 148 MemoryBlock* next_block = NULL; 149 while( my_block && my_block->nFree < nInitCount ) 150 { 151 next_block = my_block->pNext; 152 delete my_block; 153 my_block = NULL; 154 my_block = next_block; 155 } 156 DeleteCriticalSection (&allocSection); 157 DeleteCriticalSection (&freeSection); 158 } 159 nMemoryIsDeleteFlag = 1; 160 }
調用程序

1 #include "Memory.h" 2 #include <iostream> 3 #include <cassert> 4 5 using namespace std; 6 7 #define NCOUNT 24 8 #define NUNITSIZE 254 9 #define NALLSIZE nCount * nUnitSize 10 #define THREADCOUNT 64 11 12 int GloalInt = 0; 13 CRITICAL_SECTION section; 14 char *p_str = NULL; 15 16 DWORD WINAPI Fun ( LPVOID lpThreadParameter ) 17 { 18 //避免淺拷貝 19 MemoryPool &pool = *((MemoryPool *)lpThreadParameter); 20 21 EnterCriticalSection (§ion); 22 //第一個線程釋放堆內存后,不允許其他線程再訪問該堆內存; 23 //nMemoryIsDeleteFlag為標記位 24 //if ( MemoryPool::nMemoryIsDeleteFlag == 0) 25 //{ 26 //MemoryPool::nMemoryIsDeleteFlag = 1; 27 if (p_str == NULL) 28 p_str = (char *)pool.Alloc(); 29 30 p_str[0] = 'c'; 31 cout << '\t' << p_str[0] << endl; 32 33 //把p_str指向的空間釋放並歸還給內存池[注:不是歸還給系統] 34 pool.Free(p_str); 35 //MemoryPool::nMemoryIsDeleteFlag = 0; 36 //} 37 LeaveCriticalSection (§ion); 38 39 return 0; 40 } 41 42 int main() 43 { 44 45 //創建一個內存池,每個固定內存塊允許加載1024個對象(每個對象大小<=254字節). 46 //每個MemoryBlock的大小 =(MemoryBlock的大小 + 254 * 1024) ; 47 //向內存池申請NALLSIZE的內存空間,每個單元塊的大小是NUNITSIZE; 48 MemoryPool pool (NCOUNT, NUNITSIZE) ; 49 50 InitializeCriticalSection (§ion); 51 52 HANDLE hThreadHandle[THREADCOUNT]; 53 54 //p_str是指向一個NUNITSIZE大小的空間. 55 char *p_str = (char *)pool.Alloc(); 56 57 p_str[0] = 'b'; 58 59 //MAXIMUM_WAIT_OBJECTS 60 //創建線程 61 for(int i=0;i<THREADCOUNT;i++) 62 hThreadHandle[i] = CreateThread (NULL,0,Fun,&pool,0,NULL); 63 64 WaitForMultipleObjects (THREADCOUNT,hThreadHandle,TRUE,INFINITE); 65 66 for(int i=0;i<THREADCOUNT;i++) 67 { 68 if ( hThreadHandle[i] != NULL ) 69 { 70 CloseHandle (hThreadHandle[i]) ; 71 hThreadHandle[i] = NULL; 72 } 73 } 74 // do something... 75 76 //把p_str指向的空間釋放並歸還給內存池[注:不是歸還給系統] 77 //pool.Free(p_str); 78 79 80 //MemoryPool對象析構時,才是內存歸還給系統 81 82 83 return 0; 84 }
在線程方法中,得到一個主線程傳過來的內存池對象。
問題,
1、MemoryPool pool = *((MemoryPool *)lpThreadParameter);,出現淺拷貝的問題。
答:某線程結束時,析構函數中有釋放內存的代碼,其他線程再釋放就會報錯。為此,增加了一個nMemoryIsDeleteFlag的標記變量。
2、Free、析構函數、Alloc有大量申請內存,讀取內存,內存寫入和釋放內存的操作。由此,運行程序會出現線程A釋放了內存,線程B接着又訪問該內存的問題。【如:http://www.cnblogs.com/Solstice/archive/2010/02/10/dtor_meets_threads.html的問題】。
答:使用share_ptr智能指針解決。
share_ptr替代后的智能指針:
.h文件

1 #pragma once 2 3 4 #include <memory> 5 #include <iostream> 6 #include <windows.h> 7 using namespace std; 8 9 10 #define USHORT unsigned int 11 #define ULONG unsigned long 12 #define MEMPOOL_ALIGNMENT 4 13 14 #pragma warning( disable : 4291 ) 15 16 struct MemoryBlock 17 { 18 unsigned short nSize; 19 unsigned short nFree; 20 unsigned short nFirst; 21 std::shared_ptr<MemoryBlock> pNext; 22 // MemoryBlock* pNext; 23 char aData[1]; 24 25 static void* operator new(size_t, unsigned short nTypes, unsigned short nUnitSize) 26 { 27 return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize); 28 } 29 30 MemoryBlock (unsigned short nTypes = 1, unsigned short nUnitSize = 0); 31 ~MemoryBlock() {} 32 33 }; 34 35 class MemoryPool 36 { 37 private: 38 std::shared_ptr<MemoryBlock> pBlock; 39 //MemoryBlock* pBlock; 40 unsigned short nUnitSize; 41 unsigned short nInitCount; 42 unsigned short nGrowSize; 43 CRITICAL_SECTION allocSection; 44 CRITICAL_SECTION freeSection; 45 46 public: 47 //static unsigned char nMemoryIsDeleteFlag; //標記MemoryPool內存是否釋放 48 49 public: 50 MemoryPool( unsigned short nUnitSize, 51 unsigned short nInitCount = 1024, 52 unsigned short nGrowSize = 256 ); 53 ~MemoryPool(); 54 55 void* Alloc(); 56 void Free( void* p ); 57 };
.cpp文件

1 #include "Memory.h" 2 3 4 MemoryBlock::MemoryBlock(unsigned short nTypes /* = 1 */, unsigned short nUnitSize /* = 0 */) 5 : nSize( nTypes * nUnitSize ) 6 , nFree( nTypes - 1 ) 7 , nFirst(1) 8 , pNext(NULL) 9 { 10 char * pData = aData; 11 for (unsigned short i = 1; i < nTypes; i++) 12 { 13 *reinterpret_cast<unsigned short*>(pData) = i; 14 pData += nUnitSize; 15 } 16 } 17 18 //不允許其他地方修改 19 //unsigned char MemoryPool::nMemoryIsDeleteFlag = 0; 20 21 // UnitSize值不宜設置為過大值 22 MemoryPool::MemoryPool( unsigned short _nUnitSize, unsigned short _nInitCount , \ 23 unsigned short _nGrowSize ) 24 { 25 if ( _nUnitSize > 4 ) 26 nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); 27 else if ( _nUnitSize <= 2 ) 28 nUnitSize = 2; 29 else 30 nUnitSize = 4; 31 32 nUnitSize = _nUnitSize; 33 nInitCount = _nInitCount; 34 nGrowSize = _nGrowSize; 35 pBlock = NULL; 36 37 InitializeCriticalSection (&allocSection); 38 InitializeCriticalSection (&freeSection); 39 40 } 41 42 43 void * MemoryPool::Alloc() 44 { 45 //內存池沒有MemoryBlock時,申請一塊內存 46 EnterCriticalSection (&allocSection); 47 if ( !pBlock ) 48 { 49 auto pp = new(nInitCount,nUnitSize) MemoryBlock(nInitCount,nUnitSize); 50 pBlock.reset(pp); 51 LeaveCriticalSection (&allocSection); 52 //返回MemoryBlock中的第一個內存塊的地址 53 return (void*)(pBlock->aData+1); 54 } 55 56 //內存池非空時 57 MemoryBlock* pMyBlock = pBlock.get(); //從鏈表頭部pBlock開始 58 while (pMyBlock && !pMyBlock->nFree ) //搜索可用內存快 59 pMyBlock = pMyBlock->pNext.get(); 60 61 // 最后一個MemoryBlock有空閑的內存塊時 62 if ( pMyBlock ) 63 { 64 //找到第一個空閑內存塊的地址 65 char* pFree = pMyBlock->aData + (pMyBlock->nFirst * nUnitSize); 66 67 //MemoryBlock構造時,*((USHORT*)pFree)的值為所在的內存塊數; 68 //nFirst記錄本MemoryBlock的第一個空閑內存塊的計數; 69 pMyBlock->nFirst = *((USHORT*)pFree); 70 71 pMyBlock->nFree--; 72 73 LeaveCriticalSection(&allocSection); 74 return (void*)pFree; 75 } 76 //所有的MemoryBlock都占滿時 77 else 78 { 79 if ( !nGrowSize ) 80 return NULL; 81 82 pMyBlock = new(nInitCount, nUnitSize) MemoryBlock(nInitCount, nUnitSize); 83 if ( !pMyBlock ) 84 return NULL; 85 86 pMyBlock->pNext = pBlock ; 87 pBlock.reset(pMyBlock) ; 88 89 LeaveCriticalSection(&allocSection); 90 return (void*)(pMyBlock->aData); 91 } 92 93 LeaveCriticalSection(&allocSection); 94 return (void*)pBlock->aData; 95 } 96 97 //回收,內存返還給MemoryPool,而不是系統 98 void MemoryPool::Free( void* p ) 99 { 100 EnterCriticalSection (&freeSection); 101 USHORT* pfree_us; 102 USHORT pfree_index, pHead; 103 pfree_us = (USHORT *)p; 104 MemoryBlock* pMyBlock = pBlock.get(); 105 106 if(pMyBlock == NULL) //pFree不是屬於內存池管理的內存 107 { 108 LeaveCriticalSection (&freeSection); 109 return; 110 } 111 112 //定位pFree所在的塊 113 while ( ((ULONG)pMyBlock->aData > (ULONG)pfree_us) || 114 ((ULONG)pfree_us >= ((ULONG)pMyBlock->aData + pMyBlock->nSize*nUnitSize)) ) 115 { 116 pMyBlock = pMyBlock->pNext.get(); 117 } 118 119 if(pMyBlock == NULL) //pFree不是屬於內存池管理的內存 120 { 121 LeaveCriticalSection (&freeSection); 122 return; 123 } 124 125 //回收pFree 126 pMyBlock->nFree++; 127 pHead = pMyBlock->nFirst; //第一個可用索引 128 pfree_index = ( (ULONG)pfree_us-(ULONG)pMyBlock->aData )/nUnitSize; //獲取pFree的索引號 129 pMyBlock->nFirst = pfree_index; //pFree插入到可用塊首 130 *pfree_us = pHead; //之前的塊首鏈入 131 132 //判斷是否需要將Block內存返回給系統 133 // if ( (pMyBlock->nFree * nUnitSize) == pMyBlock->nSize ) 134 // { 135 // pBlock = pMyBlock->pNext; 136 // delete pMyBlock; 137 // } 138 LeaveCriticalSection (&freeSection); 139 } 140 141 142 MemoryPool::~MemoryPool() 143 { 144 // if ( nMemoryIsDeleteFlag == 0 ) 145 // { 146 // 147 // MemoryBlock* my_block = pBlock; 148 // MemoryBlock* next_block = NULL; 149 // while( my_block && my_block->nFree < nInitCount ) 150 // { 151 // next_block = my_block->pNext; 152 // delete my_block; 153 // my_block = NULL; 154 // my_block = next_block; 155 // } 156 // DeleteCriticalSection (&allocSection); 157 // DeleteCriticalSection (&freeSection); 158 // } 159 // nMemoryIsDeleteFlag = 1; 160 }
調用文件無需改變。
總結:
單線程下,因為程序是由前到后的執行順序,所以內存方面的問題調試較為容易,可以使用new/delete管理內存。多線程下,多個線程下的執行順序是隨機的,不容易調試,這樣關於內存方面的問題調試較難重現,所以多線程下考慮使用share_ptr智能指針來管理內存。
智能指針的介紹:Boost程序庫完全開發指南.pdf,其第三章有詳細介紹。