查看原文
其他

C++ 多线程的互斥锁应用RAII机制

CPP开发者 2021-06-06

(给CPP开发者加星标,提升C/C++技能)

来源:Jimmy1224
https://blog.csdn.net/c_base_jin/article/details/107520354

什么是RAII机制


RAII是Resource Acquisition Is Initialization(翻译成 “资源获取即初始化”)的简称,是C++语言的一种管理资源、避免资源泄漏的惯用法,该方法依赖构造函数资和析构函数的执行机制。


RAII的做法是使用一个类对象,在对象的构造函数中获取资源,在对象生命期内控制对资源的访问,最后在对象消失时,其析构函数来释放获取的资源;


这里的资源可以是文件句柄,内存,Event,互斥量等等,由于系统的资源是有限的,就好比自然界的石油,铁矿一样,不是取之不尽,用之不竭的。所以,我们在编程安全上,要求必须遵循以下几个步骤:


1. 申请资源


2. 使用资源


3. 释放资源


在步骤一和步骤二上,我们平时都比较容易把握,而资源的释放会因为种种编码原因容易被忽略,导致系统资源实际没有使用了,但却没有释放或者引发其他问题,影响了系统资源利用率。


没有使用RAII机制的弊端


那么我们为什么涉及资源管理时,建议使用RAII机制进行编码呢?


不推荐的编码方式片段:

while (TRUE) { //等待直到获得指定对象的所有权 EnterCriticalSection(&g_csLock); //关键代码段-begin if (g_nIndex++ < nMaxCnt) { cout << "Index = "<< g_nIndex << " "; cout << "Thread2 is runing" << endl; //权限释放,容易忘记 LeaveCriticalSection(&g_csLock); } else { //权限释放,容易忘记 LeaveCriticalSection(&g_csLock); //关键代码段-end break; } }

之所以不推荐这样的编码方式是因为EnterCriticalSection/LeaveCriticalSection必须配对使用,很需要依赖人,无法根本上解决问题,如果LeaveCriticalSection函数没有执行或者忘记添加该API很容易引发问题。


互斥锁应用RAII机制


为了从根本上解决问题,减少人为因素引发应用系统问题或者资源泄漏,在关键代码段和互斥量这两种锁上示范了如何应用RAII机制,简化多线程互斥编码。


关键代码段初始化和锁接口:

class CSLock{public: CSLock() { //构造函数时初始化关键代码段对象,获取资源 InitializeCriticalSection(&m_csLock); }
~CSLock() { //析构函数时释放为关键代码段对象分配的所有资源,释放资源 DeleteCriticalSection(&m_csLock); } //生命周期内实现对象资源的管理(Lock/Unlock),使用资源 void Lock() { EnterCriticalSection(&m_csLock); }
void Unlock() { LeaveCriticalSection(&m_csLock); } //阻止锁的拷贝和赋值private: CSLock (const CSLock& ); CSLock& operator = (const CSLock&);private: CRITICAL_SECTION m_csLock; };

创建互斥量对象和锁接口:

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机制管理锁对象的占用和释放,建议简化锁的应用,实现资源的自动回收

template<class T>class CLockGuard{public: CLockGuard(T& locker) :m_lockerObj(locker) { m_lockerObj.Lock(); }
~CLockGuard() { m_lockerObj.Unlock(); }private: T& m_lockerObj; //必须是引用类型 确保使用的是全局锁,否则锁不住};

具体示例:

#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; //RAII用法,创建lock对象的同时执行lock操作,析构后自动调用unlock操作,避免人为遗漏 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;}
//线程函数1DWORD WINAPI Thread1(LPVOID lpParameter){ while (true) { if (!AddNum(1)) { break; } } return 0;}//线程函数2DWORD WINAPI Thread2(LPVOID lpParameter){ while (true) { if (!AddNum(2)) { break; } } return 0;}
int _tmain(int argc, _TCHAR* argv[]){ 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已经应用到了该机制。点击这里查看lock_guard。


- EOF -


推荐阅读  点击标题可跳转

1、C++调用Go方法的字符串传递问题及解决方案

2、C++ vector详解

3、C++之父:C++的成功属于意料之外,C++11是转折点


关于C++ 多线程的互斥锁应用RAII机制,欢迎在评论中和我探讨。觉得文章不错,请点赞和在看支持我继续分享好文。谢谢!


关注『CPP开发者』

看精选C++技术文章 . 加C++开发者专属圈子

↓↓↓


点赞和在看就是最大的支持❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存