在C++中,线程创建可以通过Windows API 提供的函数CreateThread和CRT 提供的函数_beginthreadex,也可以使用标准库中的thread。
CreateThread
包含在#include<windows.h>中
函数原型:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数的意义分别是:
lpThreadAttributes
:用于指定线程对象的安全描述符(一般传入 NULL)。dwStackSize
:用于指定线程堆栈的大小,如果传入 0,则默认使用父进程的堆栈大小。lpStartAddress
:指向线程函数的指针,即新线程启动时要执行的函数。lpParameter
:传递给线程函数的参数。dwCreationFlags
:用于指定线程的创建标志,例如设置为 CREATE_SUSPENDED 可以让线程创建后暂停执行。lpThreadId
:用于接收新线程的线程标识符,是一个输出参数。
返回值是HANDLE类型,通过这个句柄可以对线程进行操作。
#include <iostream>
#include <Windows.h>
using namespace std;
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
std::cout << "Hello from the new thread!" << std::endl;
return 0;
}
int main() {
HANDLE hThread;
DWORD dwThreadId;
cout << "This is main thread " <<endl;
hThread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &dwThreadId);
cout << "new thread id == "<< dwThreadId << endl;
if (hThread == NULL) {
std::cerr << "Failed to create thread!" << std::endl;
return 1;
}
// 等待线程结束
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
std::cout << "Thread execution completed." << std::endl;
return 0;
}
在上面那段代码中,主线程使用createThread函数创建了一个子线程,然后主线程阻塞等待子线程执行,然后才结束。
当调用WaitForSingleObject()的时候,程序会阻塞在这里,直到被等待的对象状态变为 signaled。在等待过程中,当前线程会被挂起,直到等待超时或被等待的对象进入 signaled 状态。(当一个线程正在运行时,管理它内核对象处于non-signaled状态;而当线程结束运行时,管理它的内核对象将处于signaled状态。)
函数原型:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
hHandle
:要等待的对象的句柄。dwMilliseconds
:等待的时间,以毫秒为单位。如果设置为 INFINITE,表示无限等待。
WaitForMultipleObjects的功能和WaitForSingleObject是相似的,只不过他可以同时等待多个线程。
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
nCount
:等待的对象句柄数量。lpHandles
:指向包含对象句柄数组的指针。bWaitAll
:指定是等待所有对象进入 signaled 状态(TRUE),还是只要有一个对象进入 signaled 状态就返回(FALSE)。dwMilliseconds
:等待的时间,以毫秒为单位。如果设置为 INFINITE,表示无限等待。
WaitForMultipleObjects
函数会等待指定的内核对象数组中的一个或全部对象进入 signaled 状态。如果 bWaitAll
参数为 TRUE,则只有当数组中所有对象都进入 signaled 状态时,函数才会返回;如果为 FALSE,则只要有一个对象进入 signaled 状态,函数就会立即返回。
#include <iostream>
#include <Windows.h>
DWORD WINAPI ThreadFunc1(LPVOID lpParam) {
std::cout << "Thread 1 executing..." << std::endl;
return 0;
}
DWORD WINAPI ThreadFunc2(LPVOID lpParam) {
std::cout << "Thread 2 executing..." << std::endl;
return 0;
}
int main() {
HANDLE hThreads[2];
DWORD dwThreadId1, dwThreadId2;
hThreads[0] = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, &dwThreadId1);
hThreads[1] = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, &dwThreadId2);
if (hThreads[0] == NULL || hThreads[1] == NULL) {
std::cerr << "Failed to create threads!" << std::endl;
return 1;
}
// 等待两个线程的执行结束
WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);
CloseHandle(hThreads[0]);
CloseHandle(hThreads[1]);
std::cout << "Both threads have completed execution." << std::endl;
return 0;
}
与createThread对应的函数是Closethread函数,他的作用是关闭利用当前线程。
_beginthreadex
除了createThread以外,还有_beginthreadex,#include<process.h>头文件中。
函数原型:
uintptr_t _beginthreadex(
void* security,
unsigned stack_size,
unsigned (__stdcall* start_address )(void*),
void* arglist,
unsigned initflag,
unsigned* thrdaddr
);
参数的意义:
security
:指向安全描述符的指针,通常可以设置为 NULL。stack_size
:指定线程堆栈的大小,通常可以设置为 0 表示使用默认堆栈大小。start_address
:线程函数的入口点地址,必须是无返回值的函数,并接受一个void*
类型的参数。arglist
:传递给线程函数的参数。initflag
:标志位,通常可以设置为 0。thrdaddr
:指向用于存储新线程标识符的变量的指针。
#include <iostream>
#include <process.h>
unsigned __stdcall ThreadFunc(void* data) {
int* ptr = reinterpret_cast<int*>(data);
std::cout << "Thread ID: " << GetCurrentThreadId() << ", Received data: " << *ptr << std::endl;
return 0;
}
int main() {
int data = 42;
uintptr_t threadHandle;
// 创建新线程
threadHandle = _beginthreadex(NULL, 0, ThreadFunc, &data, 0, NULL);
if (threadHandle == -1) {
std::cerr << "Failed to create thread." << std::endl;
return 1;
}
// 等待线程结束
WaitForSingleObject(reinterpret_cast<HANDLE>(threadHandle), INFINITE);
std::cout << "Main thread exiting." << std::endl;
return 0;
}
这两个函数的一些区别:
-
返回值类型:
CreateThread
返回一个HANDLE
类型的线程句柄,用于标识新创建的线程。_beginthreadex
返回一个uintptr_t
类型的线程标识符,用于标识新创建的线程。
-
使用方式:
CreateThread
是 Windows API 中提供的函数,在使用时需要包含<Windows.h>
头文件。_beginthreadex
是 CRT(C Runtime Library)中提供的函数,在使用时需要包含<process.h>
头文件。
-
参数类型:
CreateThread
的线程函数入口点是LPTHREAD_START_ROUTINE
类型,需要是DWORD WINAPI
形式的线程函数。_beginthreadex
的线程函数入口点是unsigned (__stdcall* start_address)(void*)
类型,也需要是unsigned __stdcall
形式的线程函数。
-
堆栈:
- 在使用
CreateThread
创建线程时,需要自行管理线程堆栈的大小。 - 使用
_beginthreadex
创建线程时,可以指定线程堆栈的大小,如果为 0 则使用默认值。
- 在使用
-
运行时库:
CreateThread
是直接调用 Windows API,不依赖于 CRT。_beginthreadex
是 CRT 中提供的函数,依赖于 CRT 运行时库。
- 回收:
- 使用CreateThread创建线程,回收线程就会自动调用ExitThread,在ExitThread回收线程的时候,不会回收C++运行时库(如strcpy()就属于C++运行时库)申请的空间,就会造成内存泄漏
- 要使用_beginthreadex创建线程,回收线程就会自动调用_endthreadex,在这个函数里会先回收申请的空间,然后再调用ExitThread;
最主要的还是关于回收的情况,所以使用_beginthreadex一般会更安全一点。
thread
在C++的标准库#include<thread>中,可以直接定义出一个thread类型的线程。
#include <iostream>
#include <thread>
using namespace std;
// 线程函数
void threadFunction() {
cout << "Hello from thread!" <<endl;
}
int main() {
// 创建一个新线程,将线程函数作为参数传递
thread t(threadFunction);
cout << "Hello from main!" <<endl;
// 等待子线程执行完毕
t.join();
return 0;
}
除了基本的线程创建和管理,std::thread
还提供了其他功能,如获取和设置线程标识、检查线程是否可执行、与其他线程交互等。此外,C++11 还引入了一系列线程相关的工具,如互斥量、条件变量等,用于实现线程同步和通信。就不详细展开了。