前言
RAII是Resource Acquisition Is Initialization(翻译成 “资源获取即初始化”)的简称,是C++语言的一种管理资源、避免资源泄漏的惯用法,该方法依赖构造函数资和析构函数的执行机制。
什么是RAII机制
RAII的做法是使用一个类对象,在对象的构造函数中获取资源,在对象生命期内控制对资源的访问,最后在对象消失时,其析构函数来释放获取的资源;
这里的资源可以是文件句柄,内存,Event,互斥量等等,由于系统的资源是有限的,就好比自然界的石油,铁矿一样,不是取之不尽,用之不竭的。所以,我们在编程安全上,要求必须遵循以下几个步骤:
1. 申请资源
2. 使用资源
3. 释放资源
在步骤一和步骤二上,我们平时都比较容易把握,而资源的释放会因为种种编码原因容易被忽略,导致系统资源实际没有使用了,但却没有释放或者引发其他问题,影响了系统资源利用率。
没有使用RAII机制的弊端
那么我们为什么涉及资源管理时,建议使用RAII机制进行编码呢?
不推荐的编码方式片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| while(TRUE) { EnterCriticalSection(&g_csLock); if(g_nIndex++ < nMaxCnt) { cout << "Index = "<< g_nIndex << " "; cout << "Thread2 is runing" << endl; LeaveCriticalSection(&g_csLock); } else { LeaveCriticalSection(&g_csLock); break; } }
|
之所以不推荐这样的编码方式是因为EnterCriticalSection/LeaveCriticalSection必须配对使用,很需要依赖人,无法根本上解决问题,如果LeaveCriticalSection函数没有执行或者忘记添加该API很容易引发问题。
互斥锁应用RAII机制
为了从根本上解决问题,减少人为因素引发应用系统问题或者资源泄漏,在关键代码段和互斥量这两种锁上示范了如何应用RAII机制,简化多线程互斥编码。
关键代码段初始化和锁接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class CSLock { public: CSLock() { InitializeCriticalSection(&m_csLock); } ~CSLock() { DeleteCriticalSection(&m_csLock); } void Lock() { EnterCriticalSection(&m_csLock); } void Unlock() { LeaveCriticalSection(&m_csLock); } private: CSLock (const CSLock& ); CSLock& operator = (const CSLock&); private: CRITICAL_SECTION m_csLock; };
|
创建互斥量对象和锁接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class CMutexLock { public: CMutexLock() { m_hMutex = CreateMutex(NULL, FALSE, NULL); } ~CMutexLock() { CloseHandle(m_hMutex); } void Lock() { WaitForSingleObject(m_hMutex, INFINITE); } void Unlock() { ReleaseMutex(m_hMutex); } private: CMutexLock(const CMutexLock&); CMutexLock& operator= (const CMutexLock&); private: HANDLE m_hMutex; };
|
类模板对象,再一次使用RAII机制管理锁对象的占用和释放,建议简化锁的应用,实现资源的自动回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| template<class T> class CLockGuard { public: CLockGuard(T& locker) : m_lockerObj(locker) { m_lockerObj.Lock(); } ~CLockGuard() { m_lockerObj.Unlock(); } private: T& m_lockerObj; };
|
具体示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include "stdafx.h" #include <iostream> #include <string> #include <Windows.h> CSLock g_csLock; CMutexLock g_Mutex; int g_nIndex = 0; const int nMaxCnt = 30; BOOL AddNum(int tid) { BOOL bRet = TRUE; CLockGuard<CMutexLock> lock(g_Mutex); if(g_nIndex++ < nMaxCnt) { std::cout << "Index = " << g_nIndex << " "; std::cout << "thread " << tid << " is runing" << std::endl; } else { bRet = FALSE; } return bRet; } DWORD WINAPI Thread1(LPVOID lpParameter) { while(true) { if(!AddNum(1)) { break; } } return 0; } DWORD WINAPI Thread2(LPVOID lpParameter) { while(true) { if(!AddNum(2)) { break; } } return 0; } int main() { HANDLE harThread[2] = {NULL,NULL}; harThread[0] = CreateThread(NULL, 0, Thread1, NULL, 0, NULL); harThread[1] = CreateThread(NULL, 0, Thread2, NULL, 0, NULL); WaitForMultipleObjects(2, harThread, TRUE, INFINITE); for(int i = 0; i < 2; i++) { CloseHandle(harThread[i]); } return 0; }
|
这里使用了CLockGuard模板类来进一步简化多线程锁的编码,既实现了代码复用也保证了编码安全。其实,这编码方式在C++11中lock_guard已经应用到了该机制。