多线程下的内存释放问题
问题由来,
考虑设计一个内存池类,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,其第三章有详细介绍。