多線程中的線程同步可以使用,CreateThread,CreateMutex 互斥鎖實現線程同步,通過臨界區實現線程同步,Semaphore 基於信號實現線程同步,CreateEvent 事件對象的同步,以及線程函數傳遞單一參數與多個參數的實現方式。
CreateThread 實現多線程: 先來創建一個簡單的多線程實例,無參數傳遞版,運行實例會發現,主線程與子線程運行無規律。
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
cout << "thread function" << endl;
Sleep(200);
}
return 0;
}
int main(int argc,char * argv[])
{
HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
CloseHandle(hThread);
for (int x = 0; x < 10; x++)
{
cout << "main thread" << endl;
Sleep(400);
}
system("pause");
return 0;
}
beginthreadex 實現多線程: 這個方法與前面的CreateThread使用完全一致,只是在參數上面應使用void *
該參數可以強轉為任意類型,兩者實現效果完全一致。
#include <windows.h>
#include <iostream>
#include <process.h>
using namespace std;
unsigned WINAPI Func(void *arg)
{
for (int x = 0; x < 10; x++)
{
cout << "thread function" << endl;
Sleep(200);
}
return 0;
}
int main(int argc, char * argv[])
{
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, Func, NULL, 0, NULL);
CloseHandle(hThread);
for (int x = 0; x < 10; x++)
{
cout << "main thread" << endl;
Sleep(400);
}
system("pause");
return 0;
}
CreateMutex 互斥鎖實現線程同步: 使用互斥鎖可以實現單位時間內,只允許一個線程擁有對共享資源的獨占,從而實現了互不沖突的線程同步。
#include <windows.h>
#include <iostream>
using namespace std;
HANDLE hMutex = NULL; // 創建互斥鎖
// 線程函數
DWORD WINAPI Func(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
// 請求獲得一個互斥鎖
WaitForSingleObject(hMutex, INFINITE);
cout << "thread func" << endl;
// 釋放互斥鎖
ReleaseMutex(hMutex);
}
return 0;
}
int main(int argc,char * argv[])
{
HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
hMutex = CreateMutex(NULL, FALSE, "lyshark");
CloseHandle(hThread);
for (int x = 0; x < 10; x++)
{
// 請求獲得一個互斥鎖
WaitForSingleObject(hMutex, INFINITE);
cout << "main thread" << endl;
// 釋放互斥鎖
ReleaseMutex(hMutex);
}
system("pause");
return 0;
}
通過互斥鎖,同步執行兩個線程函數。
#include <windows.h>
#include <iostream>
using namespace std;
HANDLE hMutex = NULL; // 創建互斥鎖
#define NUM_THREAD 50
// 線程函數1
DWORD WINAPI FuncA(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
// 請求獲得一個互斥鎖
WaitForSingleObject(hMutex, INFINITE);
cout << "this is thread func A" << endl;
// 釋放互斥鎖
ReleaseMutex(hMutex);
}
return 0;
}
// 線程函數2
DWORD WINAPI FuncB(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
// 請求獲得一個互斥鎖
WaitForSingleObject(hMutex, INFINITE);
cout << "this is thread func B" << endl;
// 釋放互斥鎖
ReleaseMutex(hMutex);
}
return 0;
}
int main(int argc, char * argv[])
{
// 用來存儲線程函數的句柄
HANDLE tHandle[NUM_THREAD];
// /創建互斥量,此時為signaled狀態
hMutex = CreateMutex(NULL, FALSE, "lyshark");
for (int x = 0; x < NUM_THREAD; x++)
{
if (x % 2)
{
tHandle[x] = CreateThread(NULL, 0, FuncA, NULL, 0, NULL);
}
else
{
tHandle[x] = CreateThread(NULL, 0, FuncB, NULL, 0, NULL);
}
}
// 等待所有線程函數執行完畢
WaitForMultipleObjects(NUM_THREAD, tHandle, TRUE, INFINITE);
// 銷毀互斥對象
CloseHandle(hMutex);
system("pause");
return 0;
}
通過臨界區實現線程同步: 臨界區與互斥鎖差不多,臨界區使用時會創建CRITICAL_SECTION臨界區對象,同樣相當於一把鑰匙,線程函數執行結束自動上交,如下是臨界區函數的定義原型。
//初始化函數原型
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
//銷毀函數原型
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
//獲取
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
//釋放
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
這一次我們不適用互斥體,使用臨界區實現線程同步,結果與互斥體完全一致,看個人喜好。
#include <windows.h>
#include <iostream>
using namespace std;
CRITICAL_SECTION cs; // 全局定義臨界區對象
#define NUM_THREAD 50
// 線程函數
DWORD WINAPI FuncA(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
//進入臨界區
EnterCriticalSection(&cs);
cout << "this is thread func A" << endl;
//離開臨界區
LeaveCriticalSection(&cs);
}
return 0;
}
int main(int argc, char * argv[])
{
// 用來存儲線程函數的句柄
HANDLE tHandle[NUM_THREAD];
//初始化臨界區
InitializeCriticalSection(&cs);
for (int x = 0; x < NUM_THREAD; x++)
{
tHandle[x] = CreateThread(NULL, 0, FuncA, NULL, 0, NULL);
}
// 等待所有線程函數執行完畢
WaitForMultipleObjects(NUM_THREAD, tHandle, TRUE, INFINITE);
//釋放臨界區
DeleteCriticalSection(&cs);
system("pause");
return 0;
}
Semaphore 基於信號實現線程同步: 通過定義一個信號,初始化信號為0,利用信號量值為0時進入non-signaled狀態,大於0時進入signaled狀態的特性即可實現線程同步。
#include <windows.h>
#include <iostream>
using namespace std;
static HANDLE SemaphoreOne;
static HANDLE SemaphoreTwo;
// 線程函數1
DWORD WINAPI FuncA(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
// 臨界區開始時設置 signaled 狀態
WaitForSingleObject(SemaphoreOne, INFINITE);
cout << "this is thread func A" << endl;
// 臨界區結束則設置為 non-signaled 狀態
ReleaseSemaphore(SemaphoreOne, 1, NULL);
}
return 0;
}
// 線程函數2
DWORD WINAPI FuncB(LPVOID lpParamter)
{
for (int x = 0; x < 10; x++)
{
// 臨界區開始時設置 signaled 狀態
WaitForSingleObject(SemaphoreTwo, INFINITE);
cout << "this is thread func B" << endl;
// 臨界區結束則設置為 non-signaled 狀態
ReleaseSemaphore(SemaphoreTwo, 1, NULL);
}
return 0;
}
int main(int argc, char * argv[])
{
// 用來存儲線程函數的句柄
HANDLE hThreadA, hThreadB;
// 創建信號量對象,並且設置為0進入non-signaled狀態
SemaphoreOne = CreateSemaphore(NULL, 0, 1, NULL);
// 創建信號量對象,並且設置為1進入signaled狀態
SemaphoreTwo = CreateSemaphore(NULL, 1, 1, NULL); // 先執行這一個線程函數
hThreadA = CreateThread(NULL, 0, FuncA, NULL,0, NULL);
hThreadB = CreateThread(NULL, 0, FuncB, NULL, 0, NULL);
// 等待兩個線程函數執行完畢
WaitForSingleObject(hThreadA, INFINITE);
WaitForSingleObject(hThreadA, INFINITE);
// 銷毀兩個線程函數
CloseHandle(SemaphoreOne);
CloseHandle(SemaphoreTwo);
system("pause");
return 0;
}
上面的一段代碼,容易產生死鎖現象,即,線程函數B執行完成后,A函數一直處於等待狀態。
執行WaitForSingleObject(semTwo, INFINITE);
會讓線程函數進入類似掛起的狀態,當接到ReleaseSemaphore(semOne, 1, NULL);
才會恢復執行。
#include <windows.h>
#include <stdio.h>
static HANDLE semOne,semTwo;
static int num;
// 線程函數A用於接收參書
DWORD WINAPI ReadNumber(LPVOID lpParamter)
{
int i;
for (i = 0; i < 5; i++)
{
fputs("Input Number: ", stdout);
//臨界區的開始 signaled狀態
WaitForSingleObject(semTwo, INFINITE);
scanf("%d", &num);
//臨界區的結束 non-signaled狀態
ReleaseSemaphore(semOne, 1, NULL);
}
return 0;
}
// 線程函數B: 用戶接受參數后完成計算
DWORD WINAPI Check(LPVOID lpParamter)
{
int sum = 0, i;
for (i = 0; i < 5; i++)
{
//臨界區的開始 non-signaled狀態
WaitForSingleObject(semOne, INFINITE);
sum += num;
//臨界區的結束 signaled狀態
ReleaseSemaphore(semTwo, 1, NULL);
}
printf("The Number IS: %d \n", sum);
return 0;
}
int main(int argc, char *argv[])
{
HANDLE hThread1, hThread2;
//創建信號量對象,設置為0進入non-signaled狀態
semOne = CreateSemaphore(NULL, 0, 1, NULL);
//創建信號量對象,設置為1進入signaled狀態
semTwo = CreateSemaphore(NULL, 1, 1, NULL);
hThread1 = CreateThread(NULL, 0, ReadNumber, NULL, 0, NULL);
hThread2 = CreateThread(NULL, 0, Check, NULL, 0, NULL);
// 關閉臨界區
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
CloseHandle(semOne);
CloseHandle(semTwo);
system("pause");
return 0;
}
CreateEvent 事件對象的同步: 事件對象實現線程同步,與前面的臨界區和互斥體有很大的不同,該方法下創建對象時,可以在自動non-signaled狀態運行的auto-reset模式,當我們設置好我們需要的參數時,可以直接使用SetEvent(hEvent)
設置事件狀態,會自動執行線程函數。
#include <windows.h>
#include <stdio.h>
#include <process.h>
#define STR_LEN 100
// 存儲全局字符串
static char str[STR_LEN];
// 設置事件句柄
static HANDLE hEvent;
// 統計字符串中是否存在A
unsigned WINAPI NumberOfA(void *arg)
{
int cnt = 0;
// 等待線程對象事件
WaitForSingleObject(hEvent, INFINITE);
for (int i = 0; str[i] != 0; i++)
{
if (str[i] == 'A')
cnt++;
}
printf("Num of A: %d \n", cnt);
return 0;
}
// 統計字符串總長度
unsigned WINAPI NumberOfOthers(void *arg)
{
int cnt = 0;
// 等待線程對象事件
WaitForSingleObject(hEvent, INFINITE);
for (int i = 0; str[i] != 0; i++)
{
if (str[i] != 'A')
cnt++;
}
printf("Num of others: %d \n", cnt - 1);
return 0;
}
int main(int argc, char *argv[])
{
HANDLE hThread1, hThread2;
// 以non-signaled創建manual-reset模式的事件對象
// 該對象創建后不會被立即執行,只有我們設置狀態為Signaled時才會繼續
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
fputs("Input string: ", stdout);
fgets(str, STR_LEN, stdin);
// 字符串讀入完畢后,將事件句柄改為signaled狀態
SetEvent(hEvent);
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
//non-signaled 如果不更改,對象繼續停留在signaled
ResetEvent(hEvent);
CloseHandle(hEvent);
system("pause");
return 0;
}
線程函數傳遞單個參數: 線程函數中的定義中LPVOID
允許傳遞一個參數,只需要在縣城函數中接收並強轉(int)(LPVOID)port
即可。
#include <stdio.h>
#include <Windows.h>
// 線程函數接收一個參數
DWORD WINAPI ScanThread(LPVOID port)
{
// 將參數強制轉化為需要的類型
int Port = (int)(LPVOID)port;
printf("[+] 端口: %5d \n", port);
return 1;
}
int main(int argc, char* argv[])
{
HANDLE handle;
for (int port = 0; port < 100; port++)
{
handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ScanThread, (LPVOID)port, 0, 0);
}
WaitForSingleObject(handle, INFINITE);
system("pause");
return 0;
}
線程函數傳遞多參數: 如果想在線程函數中傳遞多個參數,則需要傳遞一個結構指針,通過線程函數內部強轉為結構類型后,取值,這個案例花費了我一些時間,網上也沒找到合適的解決方法,或找到的都是歪瓜裂棗瞎轉的東西,最后還是自己研究了一下寫了一個沒為題的。
其主要是線程函數中調用的參數會與下一個線程函數結構相沖突,解決的辦法時在每次進入線程函數時,自己拷貝一份,每個人使用自己的那一份,才可以避免此類事件的發生,同時最好配合線程同步一起使用,如下時線程掃描器的部分代碼片段。
#include <stdio.h>
#include <windows.h>
typedef struct _THREAD_PARAM
{
char *HostAddr; // 掃描主機
DWORD dwStartPort; // 端口號
}THREAD_PARAM;
// 這個掃描線程函數
DWORD WINAPI ScanThread(LPVOID lpParam)
{
// 拷貝傳遞來的掃描參數
THREAD_PARAM ScanParam = { 0 };
// 這一步很重要,如不拷貝,則會發生重復賦值現象,導致掃描端口一直都是一個。
// 坑死人的玩意,一開始我始終沒有發現這個問題。sb玩意!!
MoveMemory(&ScanParam, lpParam, sizeof(THREAD_PARAM));
printf("地址: %-16s --> 端口: %-5d 狀態: [Open] \n", ScanParam.HostAddr, ScanParam.dwStartPort);
return 0;
}
int main(int argc, char *argv[])
{
THREAD_PARAM ThreadParam = { 0 };
ThreadParam.HostAddr = "192.168.1.10";
for (DWORD port = 1; port < 100; port++)
{
ThreadParam.dwStartPort = port;
HANDLE hThread = CreateThread(NULL, 0, ScanThread, (LPVOID)&ThreadParam, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
}
system("pause");
return 0;
}