大家都知道開多線程的的目的是爲了提高程序的處理效率,但是多線程會存在訪問同一個資源會出現數據混亂,當然多個線程只讀這一個共享資源肯定是沒有問題的,但是修改的話肯定會有問題,所以需要同步的概念windows提供了多個同步對象
同步模式有兩種:
1三環(用戶模式):原子操作, 關鍵段
2內核同步對象:事件, 信號, 互斥體
三環的同步手段,效率明顯高於內核同步對象
1原子操作
- 除了當前線程之外,沒有其他任何線程會同時訪問該資源
- 單值操作:只操作單個變量
int g_nVal = 0;
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
for (int i = 0; i < 0x10000; ++i)
{
//++g_nVal;
InterlockedExchangeAdd((LPLONG)&g_nVal, 1);//原子操作加法
}
printf(" \r\n %08x \r\n", g_nVal);
return 0;
}
2關鍵段/臨界區
通過給線程中的部分區域加鎖解鎖,保證該線程對此資源的獨有訪問權。
語法
- InitializeCriticalSection(); //初始化關鍵段(臨界區)
EnterCriticalSection()
進入臨界區(加鎖)- 如果其他線程正佔用CRITICAL_SECTION ,則等待,線程阻塞在這裏直到獲取所有權限纔會繼續執行,如果沒有佔用,則返回, 並上鎖(獲取到所有權)
- 在同一個線程重複調用,會增加引用計數,需要對應次數的
LeaveCriticalSection
來解鎖,否則CRITICAL_SECTION 一直是佔用狀態(一直是上鎖狀態),其他線程就會獲取不到所有權
TryEnterCriticalSection
嘗試加鎖- 返回值若爲TRUE,進入關鍵段,並上鎖
- 返回值若爲FALSE,其他線程在使用,無論它是否獲得臨界區的所有權,線程不會阻塞在這裏
LeaveCriticalSection()
解鎖
#include <stdio.h>
#include <windows.h>
CRITICAL_SECTION cs;
int g_nVal = 0;
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
for (int i = 0; i < 0x10000; ++i)
{
//檢查cs是否是佔用狀態,如果佔用(鎖着),則等待,如果沒有鎖着,則返回, 並上鎖
//EnterCriticalSection(&cs);
//TRUE, 獲得所有權, FALSE,其他線程在使用中,EnterCriticalSection和TryEnterCriticalSection區別在於 TryEnterCriticalSection立即返回,無論它是否獲得臨界區的所有權,線程不會阻塞在這裏
if (TryEnterCriticalSection(&cs))
{
++g_nVal;
//解鎖,讓出所有權
LeaveCriticalSection(&cs);
}
else
{
//因爲線程不會阻塞,他會繼續執行,所以會++i,要等線程切換回來的時候,他的執行循環次數是原來切換的時候,所以要--
--i;
}
}
printf("%x\n", g_nVal);
return 0;
}
int main()
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
char szMsg[80];
//初始化臨界區
InitializeCriticalSection(&cs);
// 在同一個線程調用,會增加引用計數,幾次enter,需要對應次數的leave,否則鎖一直是上鎖狀態,其他線程就會獲取不到所有權
EnterCriticalSection(&cs);
EnterCriticalSection(&cs);
EnterCriticalSection(&cs);
hThread = CreateThread(
NULL,
0,
ThreadFunc,
&dwThrdParam,//線程參數
0,
&dwThreadId);
hThread = CreateThread(
NULL,
0,
ThreadFunc,
&dwThrdParam,//線程參數
0,
&dwThreadId);
Sleep(10000);
LeaveCriticalSection(&cs);
LeaveCriticalSection(&cs);
LeaveCriticalSection(&cs);
system("pause");
DeleteCriticalSection(&cs);
}
3內核同步對象
概念
內核同步對象有兩種狀態,通過他的兩種狀態來達到線程的同步
- state(狀態)
- signaled :已觸發
- no-signal :未觸發
- process/thread
- signaled :結束運行
- no-signal :正在運行
檢測狀態方式:
WaitForSingleObject -- 等待一個對象,如果對象的狀態是signaled就結束等待,函數繼續向下執行
WaitForMultipleObjects -- 等待多個對象
WaitForSingleObject
等待一個對象
if (!CreateProcess(NULL, // No module name (use command line).
szParam, // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
CREATE_NEW_CONSOLE, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
printf("CreateProcess failed.");
return 0;
}
printf("開始等待 \r\n");
while (true)
{
//參數1等待對象的句柄,參數2等待時間
DWORD dwRet = WaitForSingleObject(pi.hProcess, 1000);
if (dwRet == WAIT_OBJECT_0)
{
printf("子進程結束 \r\n");
break;
}
else if (dwRet == WAIT_TIMEOUT)
{
printf("等待超時 \r\n");
}
WaitForMultipleObjects
等待多個對象
DWORD dwThreadId, dwThrdParam = 1;
const int nCountOfThread = 5;
HANDLE hThreads[nCountOfThread];
char szMsg[80];
//創建多線程
for (int i = 0; i < nCountOfThread; ++i)
{
hThreads[i] = CreateThread(
NULL,
0,
ThreadFunc,
(LPVOID)i,
0,
&dwThreadId);
}
DWORD dwRet = WaitForMultipleObjects(
nCountOfThread,//句柄數組數量
hThreads,//句柄數組
FALSE, //是否等待所有對象
INFINITE);//等待時間,INFINITE表示一直等待
1事件(Event)
CreateEvent
創建事件對象SetEvent
將事件對象設置爲已觸發(開鎖)狀態ResetEvent
將事件對象設置爲未觸發(關鎖)狀態OpenEvent
通過名字打開一個事件對象CloseHandle
關閉事件
Handle hEvent = CreateEvent(
NULL, //句柄是否被繼承
TRUE, //TRUE:手動修改事件的狀態 FALSE:自動修改事件狀態(遇到 WaitForSingleObject若爲已觸發,會修改成未觸發)
FALSE, //TRUE:默認已觸發 FALSE:默認未觸發
_T("cr33event"));
//已觸發,開鎖(事件創建時必須先設置爲手動修改)
SetEvent(hEvent);
//未觸發,關鎖(事件創建時必須先設置爲手動修改)
ResetEvent(hEvent);
//通過事件名字打開
Handle hNewEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, _T("cr33event"));
//關閉事件
CloseHandle(hEvent);
2信號(Semaphore)
多用於處理多個線程共享多於一個資源的情況,常見於池技術,多用於生產者消費者模式
語法
CreateSemaphore
創建信號WaitForSingleObject
- 等待成功,信號量個數減一
- 信號量的個數爲0,信號量處於no-signale狀態,此時纔會等待
ReleaseSem
釋放信號,信號量個數加一
#include <windows.h>
#include <conio.h>
#include <ctime>
#include <stdio.h>
HANDLE g_hSemphore = NULL;
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
DWORD dwIdx = (DWORD)lpParam;
while (TRUE)
{
//如果信號數量大於0就返回並把信號減一,等待可用的信號, 如果成功,則可用信號的個數減一
WaitForSingleObject(g_hSemphore, INFINITE);
printf("%d 教室開始答疑 \r\n", dwIdx);
int nCnt = rand() % 0x100;
for (int i = 0; i < nCnt; ++i)
{
Sleep(100);
}
printf("%d 教室答疑結束 \r\n", dwIdx);
//釋放可用信號,指定的信號量增加指定的值,這邊是增加1個信號
ReleaseSemaphore(g_hSemphore, 1, NULL);
}
return dwIdx;
}
int main()
{
srand(time(NULL));
DWORD dwThreadId, dwThrdParam = 1;
const int nCountOfThread = 4; //四個教室,四個線程
HANDLE hThreads[nCountOfThread];
g_hSemphore = CreateSemaphore(
NULL,
2,//初始信號是兩個
4,//最多可以有4個信號
NULL
);
for (int i = 0; i < nCountOfThread; ++i)
{
hThreads[i] = CreateThread(
NULL,
0,
ThreadFunc,
(LPVOID)i,
0,
&dwThreadId);
}
system("pause");
return 0;
}
3互斥體
多個線程使用互斥體,只有一個線程擁有互斥體的權限
語法
CreateMutex
創建互斥體WaitForSingleObject
獲取互斥體的使用權,並修改互斥體的狀態爲no-signalReleaseMutex
釋放互斥體的使用權
#include <windows.h>
#include <stdio.h>
int g_nVal = 0;
HANDLE g_hMutex = NULL;
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
for (int i = 0; i < 0x10000; ++i)
{
//等待互斥體,如果成功,則獲取互斥體的權限,並將互斥體狀態改爲no-signal
WaitForSingleObject(g_hMutex, INFINITE);
++g_nVal;
//釋放互斥體,給其它線程使用
ReleaseMutex(g_hMutex);
}
printf("%08x\n", g_nVal);
return 0;
}
int main()
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread[2];
g_hMutex = CreateMutex(NULL, // 安全屬性
TRUE,// 爲True創建互斥體的線程擁有互斥體的權限,無信號狀態
NULL);// 跨進程使用需要填名字
hThread[0] = CreateThread(
NULL,
0,
ThreadFunc,
&dwThrdParam,
0,
&dwThreadId);
hThread[1] = CreateThread(
NULL,
0,
ThreadFunc,
&dwThrdParam,
0,
&dwThreadId);
Sleep(5000);//因爲主線程獲取了互斥體,所以等5秒後釋放互斥體,互斥體信號重置爲signal(有信號狀態)
//釋放互斥體,給其它線程使用
ReleaseMutex(g_hMutex);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
system("pause");
return 0;
}