多线程下的内存释放问题

问题由来,

考虑设计一个内存池类,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 };
View Code

.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 }
View Code

调用程序

 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 (&section);
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 <<  '	' << p_str[0] << endl;
32 
33         //把p_str指向的空间释放并归还给内存池[注:不是归还给系统]
34         pool.Free(p_str);
35         //MemoryPool::nMemoryIsDeleteFlag = 0;
36     //}
37     LeaveCriticalSection (&section);
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 (&section);
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 }
View Code

在线程方法中,得到一个主线程传过来的内存池对象。

问题,

  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 };
View Code

.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 }
View Code

调用文件无需改变。

总结:

  单线程下,因为程序是由前到后的执行顺序,所以内存方面的问题调试较为容易,可以使用new/delete管理内存。多线程下,多个线程下的执行顺序是随机的,不容易调试,这样关于内存方面的问题调试较难重现,所以多线程下考虑使用share_ptr智能指针来管理内存。

智能指针的介绍:Boost程序库完全开发指南.pdf,其第三章有详细介绍。

  

原文地址:https://www.cnblogs.com/xuxu8511/p/3146761.html