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

Posted Jimmy1224

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程的互斥锁应用RAII机制相关的知识,希望对你有一定的参考价值。

什么是RAII机制

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

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

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

  1. 申请资源
  2. 使用资源
  3. 释放资源

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

没有使用RAII机制的弊端

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

大家可以查看以下两篇文章中的代码,我在这两篇文章中都是直接使用系统API进行资源操作,没有使用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;


//线程函数1
DWORD WINAPI Thread1(LPVOID lpParameter)

    while (true)
    
        if (!AddNum(1))
        
            break;
        
    
    return 0;

//线程函数2
DWORD 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

以上是关于多线程的互斥锁应用RAII机制的主要内容,如果未能解决你的问题,请参考以下文章

C++封装互斥锁和基于RAII机制能自动解锁的互斥锁

多线程互斥锁读写锁

多线程中的锁机制

Linux多线程中互斥锁读写锁自旋锁条件变量信号量详解

多线程编程之自旋锁

多线程编程之自旋锁