临界区域:多个线程都可访问的数据或者代码
数据脏:多个线程同时访问临界区,有可能导致代码出错或者数据混乱。
举例:
#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <iostream>
using namespace std;
int n = 0;
DWORD WINAPI func1() {
for (int i = 0; i < 500000; i++) {
n++;
}
return 0;
}
DWORD WINAPI func2() {
for (int i = 0; i < 500000; i++) {
n++;
}
return 0;
}
int main()
{
char m[1024] = "xidian university";
HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func1,NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func2, NULL, NULL, NULL);
WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h2, INFINITE);
printf("n == %d\n", n);
while (1);
return 0;
}
实验结果和原因分析见:https://blog.csdn.net/weixin_43415644/article/details/99940624
0x00 解决临界区数据脏的问题:
线程同步:所谓线程同步,就是让多个线程在争用资源时不会出问题。
操作系统的7种状态:
用户态:同一个进程中的线程并发。
- 原子锁:Interlocked
- 读写锁:SRWLock
- 临界区:CriticalSection
内核态:跨进程的线程并发
- 事件Event
- 互斥Mutex
- 旗语(信号量) Semaphore
First.用户态:实现同一进程的不同线程同步
0x01 原子锁:
每一步操作都是原子操作(不可分割)。
例如:
InterlockedAnd 与
InterlockedOr 或
InterlockedXor 异或
InterlockedIncrement 自增
InterlockedDecrement 自减
InterlockedAdd 加
将n++修改为InterlockedIncrement即可
#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <iostream>
using namespace std;
int n = 0;
DWORD WINAPI func1() {
for (int i = 0; i < 500000; i++) {
//n++;
InterlockedIncrement((unsigned long long*)&n);
}
return 0;
}
DWORD WINAPI func2() {
for (int i = 0; i < 500000; i++) {
//n++;
InterlockedIncrement((unsigned long long*)&n);
}
return 0;
}
int main()
{
char m[1024] = "xidian university";
HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func1, NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func2, NULL, NULL, NULL);
WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h2, INFINITE);
printf("n == %d\n", n);
while (1);
return 0;
}
0x02 临界区
1.创建临界区:创建一个CRITICAL_SECTION类型的 变量
2.初始化临界区:给创建好CRITICAL_SECTION变量赋值
3.使用临界区
3.1 进入临界区:EnterCriticalSection()
3.2离开临界区:LeaveCriticalSection()
4.删除临界区:DeleteCriticalSection()
临界区如何解决临界区域数据脏的问题呢?在一个线程进入临界区之后,离开临界区之前,其他线程无权读写这一个内存区域。
#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <iostream>
using namespace std;
int n = 0;
CRITICAL_SECTION section;
DWORD WINAPI func1() {
for (int i = 0; i < 500000; i++) {
EnterCriticalSection(§ion);
n++;//各种操作
LeaveCriticalSection(§ion);
}
return 0;
}
DWORD WINAPI func2() {
for (int i = 0; i < 500000; i++) {
EnterCriticalSection(§ion);
n++;
LeaveCriticalSection(§ion);
}
return 0;
}
int main()
{
InitializeCriticalSection(§ion);
HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func1, NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func2, NULL, NULL, NULL);
WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h2, INFINITE);
printf("n == %d\n", n);
DeleteCriticalSection(§ion);
while (1);
return 0;
}
0x03 读写锁:
读读相容:对于同一段内存,一个线程进行读操作的时候,另一个线程不能进行读操作。
读写相斥:对于同一段内存,一个线程进行读(写)操作的时候,另一个线程不能进行写(读)操作。
写写相斥:对于同一段内存,一个线程进行写操作的时候,另一个线程不能进行写操作。
如何使用?
- 创建读写锁:
- 初始化读写锁:
- 使用读写锁:
- 请求读锁:AcquireSRWLockShared
- 释放读锁:ReleaseSRWLockShared
- 请求写锁:AcquireSRWLockExclusive
- 释放写锁:ReleaseSRWLockExclusive
例如:
读读相容:读锁和读锁是相容的,两把读锁可以同时操作同一段内存
#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <iostream>
using namespace std;
int n = 0;
SRWLOCK srwLock;
DWORD WINAPI func2() {
for (int i = 0; i < 500000; i++) {
AcquireSRWLockShared(&srwLock);
n++;
ReleaseSRWLockShared(&srwLock);
}
return 0;
}
DWORD WINAPI func3() {
for (int i = 0; i < 500000; i++) {
AcquireSRWLockShared(&srwLock);
n++;
ReleaseSRWLockShared(&srwLock);
}
return 0;
}
int main()
{
InitializeSRWLock(&srwLock);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func2, NULL, NULL, NULL);
HANDLE h3 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func3, NULL, NULL, NULL);
WaitForSingleObject(h2, INFINITE);
WaitForSingleObject(h3, INFINITE);
printf("n == %d\n", n);
while (1);
return 0;
}
读写相斥:
读锁和写锁不能同时对同一段内存进行操作。
#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <iostream>
using namespace std;
int n = 0;
SRWLOCK srwLock;
DWORD WINAPI func1() {
for (int i = 0; i < 500000; i++) {
//EnterCriticalSection(§ion);
n++;//各种操作
//LeaveCriticalSection(§ion);
}
return 0;
}
DWORD WINAPI func2() {
for (int i = 0; i < 500000; i++) {
AcquireSRWLockShared(&srwLock);
n++;
ReleaseSRWLockShared(&srwLock);
}
return 0;
}
DWORD WINAPI func3() {
for (int i = 0; i < 500000; i++) {
AcquireSRWLockExclusive(&srwLock);
n++;
ReleaseSRWLockExclusive(&srwLock);
}
return 0;
}
int main()
{
InitializeSRWLock(&srwLock);
char m[1024] = "xidian university";
//HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func1, NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func2, NULL, NULL, NULL);
HANDLE h3 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func3, NULL, NULL, NULL);
//WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h2, INFINITE);
WaitForSingleObject(h3, INFINITE);
printf("n == %d\n", n);
while (1);
return 0;
}
执行结果:n == 1000000
写写相斥:
#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <iostream>
using namespace std;
int n = 0;
SRWLOCK srwLock;
DWORD WINAPI func1() {
for (int i = 0; i < 500000; i++) {
//EnterCriticalSection(§ion);
n++;//各种操作
//LeaveCriticalSection(§ion);
}
return 0;
}
DWORD WINAPI func2() {
for (int i = 0; i < 500000; i++) {
AcquireSRWLockExclusive(&srwLock);
n++;
ReleaseSRWLockExclusive(&srwLock);
}
return 0;
}
DWORD WINAPI func3() {
for (int i = 0; i < 500000; i++) {
AcquireSRWLockExclusive(&srwLock);
n++;
ReleaseSRWLockExclusive(&srwLock);
}
return 0;
}
int main()
{
InitializeSRWLock(&srwLock);
char m[1024] = "xidian university";
//HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func1, NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func2, NULL, NULL, NULL);
HANDLE h3 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)func3, NULL, NULL, NULL);
//WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h2, INFINITE);
WaitForSingleObject(h3, INFINITE);
printf("n == %d\n", n);
while (1);
return 0;
}
选择顺序:
如果每个线程进行都是简单操作,直接选择使用原子锁
如果每个线程进行是比较复杂的数据操作,选择读写锁,更加灵活
如果不是数据操作,而是一段代码。选择临界区更好一些。
Secondly:内核态——实现不同进程之间的线程同步。
0x01 事件:
操作步骤:
- 创建事件:CreateEvent
CreateEventA( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,//事件属性,一般写NULL表示默认 _In_ BOOL bManualReset,//是否重新设置 _In_ BOOL bInitialState,//初始化状态 _In_opt_ LPCSTR lpName//事件的名字 );
- 打开事件:OpenEvent
- 设置事件:SetEvent
- 重置事件:ResetEvent
例如:
f1函数和f2函数都在等待信号,只有等待到信号才会执行之后的语句。f3()函数用来每隔两秒发送一次信号。Reset函数用来杀死信号。
// 02-内核态线程同步.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <Windows.h>
using namespace std;
HANDLE hEvent;
void f1() {
while (1) {
WaitForSingleObject(hEvent, INFINITE);
cout << "线程1" << endl;
ResetEvent(hEvent);
}
}
void f2()
{
while (1) {
WaitForSingleObject(hEvent, INFINITE);
cout << "线程2" << endl;
ResetEvent(hEvent);
}
}
void f3() {
while (1) {
SetEvent(hEvent);
Sleep(2000);
}
}
int main() {
hEvent = CreateEvent(NULL, false, true, TEXT("Event1"));
HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)f1, NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)f2, NULL, NULL, NULL);
f3();
WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h1, INFINITE);
return 0;
}
0x02 互斥
1.创建互斥量:CreateMutex
2.释放互斥量:ReleaseMutex
多个线程会争夺互斥量,只有一个线程可以争的到互斥量。如果这个线程不释放互斥量,那么它将一直占有。
// 02-内核态线程同步.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <Windows.h>
using namespace std;
HANDLE hMutex;
void f1() {
while (1) {
WaitForSingleObject(hMutex, INFINITE);
cout << "线程1" << endl;
//ReleaseMutex(hMutex);
}
}
void f2()
{
while (1) {
WaitForSingleObject(hMutex, INFINITE);
cout << "线程2" << endl;
ReleaseMutex(hMutex);
}
}
int main() {
hMutex = CreateMutex(NULL, false, NULL);
HANDLE h1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)f1, NULL, NULL, NULL);
HANDLE h2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)f2, NULL, NULL, NULL);
WaitForSingleObject(h1, INFINITE);
WaitForSingleObject(h1, INFINITE);
return 0;
}
0x03 旗语
- 创建旗语:CreateSemaphore,创建了一个数字,可以设置数字的值
- WaitForSingleObject 数字减少1之后,结果大于0,减少并立刻返回。数字减少1之后,结果小于0,直接阻塞。
- ReleaseSemaphore 给数字做加法。