多線程下的內存釋放問題


問題由來,

考慮設計一個內存池類,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 <<  '\t' << 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,其第三章有詳細介紹。

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM