上两篇C++新特性29_线程同步问题的解决思路(原子操作、WindowsAPI实现原子操作、针对特定操作的WindowsAPI、如何解决同步的一般问题、 实际场景介绍解决同步问题的思路、)及C++新特性30_windows api解决线程同步(临界区、手动加EnterCriticalSection(&),LeaveCriticalSection(&)、临界区大小即颗粒度、如何自动加锁)中介绍了Windows API解决同步问题的两种方法,一种是针对特定操作的API,另一种是使用临界区的形式实现,C++11对临界区的方法进行封装之后,可以在语法上避免忘记解锁等操作。
本篇介绍如何自己利用C++封装自己实现线程同步,主要用到了2个类,一个类实现基本操作的封装,一个类利用构造和析构实现加锁和解锁的自动化。
重点: 利用引用实现在一个类中对另一个类的唯一对象的调用方法如下
:
构造函数利用私有类中的CMyMutex& m_Mutex;
作为参数,利用外部传入的对象g_mtx
进行初始化,由于使用的是引用,保证了操作的对象指向了外部传入的对象g_mtx
。
引用的参考地址:C++57个入门知识点_06_ 什么是引用(需改变外部内容,用指针存在判断地址的问题、引用本质给外部内容起别名、fool(int& n) 外部函数:int& refN=n fool(refN/n))
C++新特性31_利用C++自己封装线程同步锁
1.封装临界区方法
首先新建一个CMyMutex类
对临界区方法进行封装,利用类的构造和析构将对象进行构造和析构,在类中创建加锁解锁函数。
(1)CMyMutex.h
:
#pragma once
#include <windows.h>
class 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 <tchar.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>
#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;
}
运行结果:
上述过程实现了临界区方法的封装,但是仍然使用的是手动的加锁和解锁功能,一旦忘记添加就会造成和上篇中忘记添加解锁造成无法跳出的问题。
2.自动实现加锁和解锁
如何实现C++语法上预防出现忘记解锁的情况出现呢?
这里利用C++中对象的构造和析构实现,创建另一个类CMyLockGuard
,将加锁和解锁利用构造(对象创建)与析构(出作用域)自动进行。
(1)CMyMutex.h中创建CMyLockGuard类
:
#pragma once
#include <windows.h>
class 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 <tchar.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>
#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)整个程序的思维导图
:
运行结果:
3.减小颗粒度
上述程序利用构造和析构实现了自动在作用域中加锁与解锁,但是整体加锁解锁的颗粒度及作用域较大,我们可以通过使用块作用域来实现减小颗粒度的目的,代码如下:
#include <tchar.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>
#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++自己封装线程同步锁