Multi-threaded mutex application RAII mechanism

What is the RAII mechanism

RAII is the abbreviation of Resource Acquisition Is Initialization (translated into "Resource Acquisition Is Initialization"). It is a C++ language idiom for managing resources and avoiding resource leaks. This method relies on the execution mechanism of constructor resources and destructors .

RAII's approach is to use a class object to obtain resources in the object's constructor, control access to resources during the object's lifetime, and finally release the obtained resources when the object disappears ;

The resources here can be file handles, memory, events, mutexes, etc., because the resources of the system are limited, just like oil and iron ore in nature, they are not inexhaustible. Therefore, in terms of programming security, we require the following steps to be followed:

  1. application resources
  2. use resources
  3. release resources

In step 1 and step 2, we are usually relatively easy to grasp, and the release of resources will be easily ignored due to various coding reasons, resulting in system resources that are actually not used, but they are not released or cause other problems , affecting the utilization of system resources Rate.

The disadvantages of not using the RAII mechanism

So why do we recommend using the RAII mechanism for encoding when it comes to resource management?

You can view the codes in the following two articles. In these two articles, I directly use the system API for resource operations, and do not use the RAII mechanism for resource management, which is very inconvenient for code reading and maintenance.

Generation and solution of multi-thread deadlock
Use key code segment to realize thread synchronization

Fragments of deprecated encoding methods:

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

The reason why such a coding method is not recommended is that EnterCriticalSection/LeaveCriticalSectionit must be used in pairs, and it is very dependent on people, which cannot fundamentally solve the problem. If LeaveCriticalSectionthe function is not executed or you forget to add the API, it will easily cause problems.

Mutex application RAII mechanism

In order to solve the problem fundamentally and reduce application system problems or resource leaks caused by human factors, it demonstrates how to apply the RAII mechanism on key code segments and mutexes to simplify multi-threaded mutual exclusion coding.

Key code segment initialization and lock interface:

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

Create a mutex object and a lock interface:

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

Class template objects, once again use the RAII mechanism to manage the occupation and release of lock objects. It is recommended to simplify the application of locks and realize the automatic recycling of resources

template<class T>
class CLockGuard
{
    
    
public:
    CLockGuard(T& locker) :m_lockerObj(locker)
    {
    
    
        m_lockerObj.Lock();
    }

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

Concrete example:

#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;
}

Running effect:
insert image description here
Judging from the output results, our lock is in effect and there is no confusion. The template class is used here CLockGuardto further simplify the coding of multi-threaded locks, which not only realizes code reuse but also ensures coding security. In fact, this coding method has been applied to this mechanism in C++11 lock_guard. Click here to view lock_guard .

Guess you like

Origin blog.csdn.net/xiao3404/article/details/107520354