臨界區域:多個線程都可訪問的數據或者代碼
數據髒:多個線程同時訪問臨界區,有可能導致代碼出錯或者數據混亂。
舉例:
#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 給數字做加法。