在Windows平臺下創建多線程有兩種方式,讀者可以使用`CreateThread`函數,或者使用`beginthreadex`函數均可,兩者雖然都可以用於創建多線程環境,但還是存在一些差異的,首先`CreateThread`函數它是`Win32 API`的一部分,而`_beginthreadex`... ...
在Windows平臺下創建多線程有兩種方式,讀者可以使用CreateThread
函數,或者使用beginthreadex
函數均可,兩者雖然都可以用於創建多線程環境,但還是存在一些差異的,首先CreateThread
函數它是Win32 API
的一部分,而_beginthreadex
是C/C++
運行庫的一部分,在參數返回值類型方面,CreateThread
返回線程句柄,而_beginthreadex
返回線程ID,當然這兩者在使用上並沒有太大的差異,但為了代碼更加通用筆者推薦使用後者,因為後者與平臺無關性更容易實現跨平臺需求。
9.1.1 CreateThread
CreateThread 函數是Windows API
提供的用於創建線程的函數。它接受一些參數,如線程的入口函數、線程的堆棧大小等,可以創建一個新的線程並返回線程句柄。開發者可以使用該句柄控制該線程的運行狀態。需要註意,在使用CreateThread
創建線程時,線程入口函數的返回值是線程的退出碼,而不是線程執行的結果值。
CreateThread 函數原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
參數說明:
- lpThreadAttributes:指向
SECURITY_ATTRIBUTES
結構體的指針,指定線程安全描述符和訪問許可權。通常設為NULL,表示使用預設值。 - dwStackSize:指定線程堆棧的大小,以位元組為單位。如果
dwStackSize
為0,則使用預設的堆棧大小。(註:在32位程式下,該值的預設大小為1MB;在64位程式下,該值的預設大小為4MB) - lpStartAddress:指向線程函數的指針,這個函數就是線程執行的入口點。當線程啟動時,系統就會調用這個函數。
- lpParameter:指定傳遞給線程函數的參數,可以為NULL。
- dwCreationFlags:指定線程的創建標誌。通常設為0,表示使用預設值。
- lpThreadId:指向一個
DWORD
變數的指針,表示返回的線程ID號。可以為NULL。
CreateThread 函數將創建一個新的線程,並返回線程句柄。開發者可以使用該句柄控制該線程的運行狀態,如掛起、恢復、終止等。線程創建成功後,執行線程函數進行相應的業務處理。需要註意的是,在使用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;
}
如上所示代碼中我們線上程函數Func()
內沒有進行任何的加鎖操作,那麼也就會出現資源的爭奪現象,這些會被搶奪的資源就被稱為是臨界資源,我們可以通過設置臨界鎖來實現同一時刻內保持一個線程操作資源。
EnterCriticalSection 是Windows API提供的線程同步函數之一,用於進入一個臨界區並且鎖定該區域,以確保同一時間只有一個線程訪問臨界區代碼。
EnterCriticalSection函數的函數原型如下:
void EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
參數說明:
- lpCriticalSection:指向CRITICAL_SECTION結構體的指針,表示要進入的臨界區。
EnterCriticalSection 函數將等待,直到指定的臨界區對象可用並且已經鎖定,然後,當前線程將進入臨界區。臨界區中的代碼將在當前線程完成之前,不允許被任何其他線程執行。當線程完成臨界區的工作時,應該調用LeaveCriticalSection
函數釋放臨界區。否則,其他線程將無法進入臨界區,導致死鎖。
EnterCriticalSection 函數是比較底層的線程同步函數,需要開發者自行創建臨界區,維護臨界區的狀態併進行加鎖解鎖的操作,使用時需要註意對臨界區中的操作進行適當的封裝和處理。同時,EnterCriticalSection
函數也是比較高效的線程同步方式,對於需要頻繁訪問臨界資源的場景,可以通過使用臨界區來提高程式的性能。
#include <Windows.h>
#include <iostream>
int Global_One = 0;
// 全局定義臨界區對象
CRITICAL_SECTION g_cs;
// 定義一個線程函數
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
// 加鎖防止線程數據衝突
EnterCriticalSection(&g_cs);
for (int x = 0; x < 10; x++)
{
Global_One++;
Sleep(1);
}
// 執行完修改以後,需要釋放鎖
LeaveCriticalSection(&g_cs);
return 0;
}
int main(int argc, char * argv[])
{
// 初始化臨界區
InitializeCriticalSection(&g_cs);
HANDLE hThread[10] = { 0 };
for (int x = 0; x < 10; x++)
{
// 迴圈創建線程
hThread[x] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
}
// 等待多個線程執行結束
WaitForMultipleObjects(10, hThread, TRUE, INFINITE);
// 最後迴圈釋放資源
for (int x = 0; x < 10; x++)
{
CloseHandle(hThread[x]);
}
printf("全局變數值: %d \n", Global_One);
// 釋放鎖
DeleteCriticalSection(&g_cs);
system("pause");
return 0;
}
9.1.2 BeginThreadex
BeginThreadex 是C/C++
運行庫提供的用於創建線程的函數。它也接受一些參數,如線程的入口函數、線程的堆棧大小等,與CreateThread
不同的是,_beginthreadex
函數返回的是線程的ID,而不是線程句柄。開發者可以使用該ID
在運行時控制該線程的運行狀態。此外,_beginthreadex
函數通常與_endthreadex
配對使用,供線程退出時使用。
beginthreadex 函數的函數原型如下:
uintptr_t _beginthreadex(
void* security,
unsigned stack_size,
unsigned(__stdcall* start_address)(void*),
void* arglist,
unsigned initflag,
unsigned* thrdaddr
);
參數說明:
- security:與
Windows
安全機制相關,用於指定線程的安全屬性,一般填NULL即可。 - stack_size:指定線程的堆棧大小,以位元組為單位。如果
stack_size
為0,則使用預設的堆棧大小。 - start_address:線程函數的入口點。
- arglist:傳遞給線程函數的參數。
- initflag:線程標誌,0表示啟動線程後立即運行,
CREATE_SUSPENDED
表示啟動線程後暫停運行。 - thrdaddr:指向
unsigned
變數的指針,表示返回的線程ID號。可以為NULL。
與CreateThread
相比,_beginthreadex
函數返回線程ID而非線程句柄,使用時需要註意區分。與CreateThread
不同的是,_beginthreadex
函數接受傳遞給線程函數的參數放在arglist
中,方便傳遞多個參數。線程使用完需要調用_endthreadex
函數來關閉線程。當使用了_beginthreadex
創建的線程退出時,會調用_endthreadex
來結束線程,這裡的返回值會被當做線程的退出碼。
#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;
}
由於CreateThread()
函數是Windows
提供的API介面,在C/C++
語言另有一個創建線程的函數_beginthreadex()
該函數在創建新線程時會分配並初始化一個_tiddata
塊,這個塊用來存放一些需要線程獨享的數據,從而保證了線程資源不會發生衝突的情況,代碼只需要稍微在上面基礎上改進即可。
當然該函數同樣需要設置線程臨界區而設置方式與CreateThread
中所展示的完全一致。
#include <stdio.h>
#include <process.h>
#include <windows.h>
// 全局資源
long g_nNum = 0;
// 子線程個數
const int THREAD_NUM = 10;
CRITICAL_SECTION g_csThreadCode;
unsigned int __stdcall ThreadFunction(void *ptr)
{
int nThreadNum = *(int *)ptr;
// 進入線程鎖
EnterCriticalSection(&g_csThreadCode);
g_nNum++;
printf("線程編號: %d --> 全局資源值: %d --> 子線程ID: %d \n", nThreadNum, g_nNum, GetCurrentThreadId());
// 離開線程鎖
LeaveCriticalSection(&g_csThreadCode);
return 0;
}
int main(int argc,char * argv[])
{
unsigned int ThreadCount = 0;
HANDLE handle[THREAD_NUM];
InitializeCriticalSection(&g_csThreadCode);
for (int each = 0; each < THREAD_NUM; each++)
{
handle[each] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, &each, 0, &ThreadCount);
printf("線程ID: %d \n", ThreadCount);
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode);
system("pause");
return 0;
}
總的來說,_beginthreadex
比CreateThread
更加高級,封裝了許多細節,使用起來更方便,特別是對於傳遞多個參數的情況下,可以更簡單地傳參。
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/922df2e6.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17738611.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!