在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 许可协议。转载请注明出处!