接上篇:C++新特性29_线程同步问题的解决思路(原子操作、WindowsAPI实现原子操作、实际场景介绍解决同步问题的思路),上篇我们阐述了解决特定操作同步问题的方法,另外对于一般线程同步问题的思路就是加一把锁
,那如何找到这把锁呢?
一般而言操作系统会提供API,C++11中也提供了线程操作的方法,但是我们还是先要了解Windows系统中对线程的操作,因为C++11中的操作其实就是对Windows API的封装
。本篇就介绍在Windows中解决一般普遍线程同步的方法-临界区
。
1. 临界区
临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权,在线程退出临界区之前,系统将不给想要访问相同资源的其他任何线程进行调度。
EnterCriticalSection(&cs)
xxxx,需要被同步的代码
LeaveCriticalSection(&cs)
我们对C++11中的语法进行追溯,其本质就是调用了这两个API
,所以我们先讲内部的原理,再讲外部如何使用。
临界区理解: 临界区即我们需要保护的代码块,进来前上把锁,出来之后解锁。
使用临界区实现同步:
#include <tchar.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>
using namespace std;
int g_nData = 0;
//创建临界区对象(不是C++中对象概念,是Windows中的)等价于锁:买了一把锁
//为方便使用,将其定义为全局变量
CRITICAL_SECTION g_cs;
void foo() {
for (int i = 0; i < 100000; i++) {
//进来上锁
EnterCriticalSection(&g_cs);
g_nData++;
//出去解锁
LeaveCriticalSection(&g_cs);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
//使用前,需要对该对象初始化
InitializeCriticalSection(&g_cs);
std::thread t(foo);
for (int i = 0; i < 100000; i++) {
//进来上锁
EnterCriticalSection(&g_cs);
g_nData++;
//出去解锁
LeaveCriticalSection(&g_cs);
}
t.join();
std::cout << g_nData << std::endl;
//程序结束之前需要把锁删除掉
DeleteCriticalSection(&g_cs);
return 0;
}
运行结果:
1.1 颗粒度
上锁的颗粒度:上锁代码块区域的大小或者范围
。
当我们调整颗粒度,看运行结果:
#include <tchar.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>
using namespace std;
int g_nData = 0;
//创建临界区对象(不是C++中对象概念,是Windows中的)等价于锁
//为方便使用,将其定义为全局变量
CRITICAL_SECTION g_cs;
void foo() {
//进来上锁:锁的区域变大,执行完for循环才能解锁
EnterCriticalSection(&g_cs);
for (int i = 0; i < 100000; i++) {
g_nData++;
}
//出去解锁
LeaveCriticalSection(&g_cs);
}
int _tmain(int argc, _TCHAR* argv[])
{
//使用前,需要对该对象初始化
InitializeCriticalSection(&g_cs);
std::thread t(foo);
//进来上锁
EnterCriticalSection(&g_cs);
for (int i = 0; i < 100000; i++) {
g_nData++;
}
//出去解锁
LeaveCriticalSection(&g_cs);
t.join();
std::cout << g_nData << std::endl;
//程序结束之前需要把锁删除掉
DeleteCriticalSection(&g_cs);
return 0;
}
运行结果:
一般而言建议使用小的颗粒度,对于耗时的代码不建议使用大的颗粒度,这样就会导致其他线程要等待这个线程运行结束才可以运行,等待时间变长。
1.2 注意事项
每一个使用临界区应该在使用共享资源前调用EnterCriticalSection,使用完毕则调用LeaveCriticalSection,如果使用完毕没有解锁,就会导致线程无法跳出,其他线程无法操作同一个资源
。- 如果有若干互不相干的共享资源,则应该为每一个互不相干的资源建一个临界区。
- 当同时访问多个资源的时候,注意死锁的问题。
- 临界区的缺点是不能跨进程
这种手动上锁解锁的方式,很容易因为人为因素造成错误,如何实现系统自动上锁的操作呢?
2. 学习视频地址:windows api解决线程同步