上两篇C++新特性29_线程同步问题的解决思路(原子操作、WindowsAPI实现原子操作、针对特定操作的WindowsAPI、如何解决同步的一般问题、 实际场景介绍解决同步问题的思路、)及C++新特性30_windows api解决线程同步(临界区、手动加EnterCriticalSection(&),LeaveCriticalSection(&)、临界区大小即颗粒度、如何自动加锁)中介绍了Windows API解决同步问题的两种方法,一种是针对特定操作的API,另一种是使用临界区的形式实现,C++11对临界区的方法进行封装之后,可以在语法上避免忘记解锁等操作。
本篇介绍如何自己利用C++封装自己实现线程同步,主要用到了2个类,一个类实现基本操作的封装,一个类利用构造和析构实现加锁和解锁的自动化。
重点: 利用引用实现在一个类中对另一个类的唯一对象的调用方法如下:
构造函数利用私有类中的CMyMutex& m_Mutex;作为参数,利用外部传入的对象g_mtx进行初始化,由于使用的是引用,保证了操作的对象指向了外部传入的对象g_mtx。
首先新建一个CMyMutex类对临界区方法进行封装,利用类的构造和析构将对象进行构造和析构,在类中创建加锁解锁函数。
(1)CMyMutex.h:
#pragma once #includeclass CMyMutex { public: CMyMutex(); ~CMyMutex(); void lock(); void unlock(); private: CRITICAL_SECTION m_cs; };
CMyMutex.cpp:
#include "CMyMutex.h"
CMyMutex::CMyMutex()
{
//使用前,需要对该对象进行初始化
InitializeCriticalSection(&m_cs);
}
CMyMutex::~CMyMutex()
{
//当不在使用该锁时,需要将锁删掉
DeleteCriticalSection(&m_cs);
}
void CMyMutex::lock()
{
//进去加锁
EnterCriticalSection(&m_cs);
}
void CMyMutex::unlock()
{
//出去解锁
LeaveCriticalSection(&m_cs);
}
(2)main()中利用封装好的类进行对象创建析构及实现加锁解锁功能:
#include#include #include #include #include #include "CMyMutex.h" using namespace std; int g_nData = 0; // 创建临界区对象,等价于锁 CMyMutex g_mtx; void foo() { //进来上锁 g_mtx.lock(); for (int i = 0; i < 100000; i++) { g_nData++; } //出去解锁 g_mtx.unlock(); } int _tmain(int argc, _TCHAR* argv[]) { std::thread t(foo); //进来上锁 g_mtx.lock(); for (int i = 0; i < 100000; i++) { g_nData++; } //出去解锁 g_mtx.unlock(); t.join(); std::cout << g_nData << std::endl; return 0; }
运行结果:
上述过程实现了临界区方法的封装,但是仍然使用的是手动的加锁和解锁功能,一旦忘记添加就会造成和上篇中忘记添加解锁造成无法跳出的问题。
如何实现C++语法上预防出现忘记解锁的情况出现呢?
这里利用C++中对象的构造和析构实现,创建另一个类CMyLockGuard,将加锁和解锁利用构造与析构自动进行。
(1)CMyMutex.h中创建CMyLockGuard类:
#pragma once #includeclass CMyMutex { public: CMyMutex(); ~CMyMutex(); void lock(); void unlock(); private: CRITICAL_SECTION m_cs; }; class CMyLockGuard { public: //将对象的引用作为参数传递进CMyLockGuard构造中 CMyLockGuard(CMyMutex& mtx) :m_Mutex(mtx) { m_Mutex.lock(); } ~CMyLockGuard() { m_Mutex.unlock(); } private: //为了保证锁在使用时是唯一的,因此采用引用的形式 CMyMutex& m_Mutex; };
(2)CMyMutex.cpp中保持不变
(3)main中利用封装的CMyMutex类对象对CMyMutex类进行操作:
其思维导图如下:利用一个类对另一个类同一个对象操作的方法-使用引用
#include#include #include #include #include #include "CMyMutex.h" using namespace std; int g_nData = 0; // 创建临界区对象,等价于锁 CMyMutex g_mtx; void foo() { CMyLockGuard lg(g_mtx); for (int i = 0; i < 100000; i++) { g_nData++; } } int _tmain(int argc, _TCHAR* argv[]) { std::thread t(foo); CMyLockGuard lg(g_mtx); for (int i = 0; i < 100000; i++) { g_nData++; } t.join(); std::cout << g_nData << std::endl; return 0; }
(4)整个程序的思维导图:
运行结果:
上述程序利用构造和析构实现了自动在作用域中加锁与解锁,但是整体加锁解锁的颗粒度及作用域较大,我们可以通过使用块作用域来实现减小颗粒度的目的,代码如下:
#include#include #include #include #include #include "CMyMutex.h" using namespace std; int g_nData = 0; // 创建临界区对象,等价于锁 CMyMutex g_mtx; void foo() { { CMyLockGuard lg(g_mtx); for (int i = 0; i < 100000; i++) { g_nData++; } } } int _tmain(int argc, _TCHAR* argv[]) { std::thread t(foo); { CMyLockGuard lg(g_mtx); for (int i = 0; i < 100000; i++) { g_nData++; } } t.join(); std::cout << g_nData << std::endl; return 0; }
对象及局部变量的作用域指的即为包含其的最小{},当运行出作用域,就会自动释放内存资源。
4. 学习视频地址: 利用C++自己封装线程同步锁



