系统中,进程主要有两部分组成:进程内核对象和进程地址空间。操作系统通过进程内核对象来管理进程,进程地址空间用于维护进程所需的资源:如代码、全局变量、资源文件等。
那么线程也是有两部分组成:线程内核对象和线程堆栈。操作系统通过线程内核对象对线程进行管理,线程堆栈用于维护线程执行代码时需要的所有的函数参数和局部变量。
线程的开销远小于进程,所以在并发执行多个任务的时候,应该尽可能使用多线程 解决问题,而不是使用多进程解决问题。
创建线程
当exe程序启动的时候,操作系统会创建一个主线程,用于执行入口函数(main函数)。通过在主线程中调用对应的函数,可以创建更多的线程来执行任务。通过CreateThread
函数可以创建一个线程。
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程安全描述符
SIZE_T dwStackSize,// 初始堆栈大小,通常为 0,表示使用默认大小。
LPTHREAD_START_ROUTINE lpStartAddress,//多线程要执行的函数指针
__drv_aliasesMem LPVOID lpParameter,//传递给多线程的参数
DWORD dwCreationFlags,// 线程创建标志,通常为0,表示立即执行。CREATE_SUSPENDED表示创建线程之后,不立即调度。
LPDWORD lpThreadId //指向接收线程 ID 的变量的指针,通常为 NULL。
);
下面是同CreateThread
创建多线程并执行一个函数的简单示例代码。
#include <iostream>
#include <Windows.h>
// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam)
{
while (true)
{
std::cout << "Hello, World!" << std::endl;
Sleep(1000); // 休眠1秒
}
return 0;
}
int main()
{ // 创建线程
HANDLE hThread = CreateThread(
NULL, // 默认安全属性
0, // 默认堆栈大小
ThreadFunction, // 线程函数
NULL, // 线程函数参数
0, // 默认创建标志
NULL); // 不需要线程ID
// 防止主线程立即退出
WaitForSingleObject(hThread, INFINITE);
// 关闭线程句柄
CloseHandle(hThread);
return 0;
}
终止线程
(1)、通过线程要执行的函数正常返回(建议使用该方法)。
(2)、通过调用ExitThread
函数退出线程,一般不建议使用该函数。
(3)、通过调用TerminateThread
函数退出线程,一般也不建议使用该方法。
(4)、直接结束进程,可以间接终止线程的执行。
创建线程二
上面代码,我们使用CreateThread
函数创建了线程,该函数是Windows的一个函数,并不是C/C++库中提供的函数。我们可以使用_beginthreadex
函数创建线程,通过_endthreadex
函数结束线程。这是我非常推荐和常用的创建线程的方式。_beginthreadex
的函数原型如下,和CreateThread
函数原型相差不是很大。
uintptr_t _beginthreadex(
void* _Security,//线程安全描述符
unsigned _StackSize,//线程的初始堆栈大小,通常为 0,表示使用默认大小。
_beginthreadex_proc_type _StartAddress,//线程函数的指针,即线程的入口点。
void* _ArgList,// 传递给线程函数的参数,可以为 NULL。
unsigned _InitFlag,//线程创建标志,通常为 0。
unsigned* _ThrdAddr//用于接收线程 ID 的变量的指针,可以为 NULL。
);
下面是使用_beginthreadex
和_endthreadex
的一个简单示例。
#include <iostream>
#include <Windows.h>
#include <process.h>
// 线程函数
unsigned __stdcall ThreadFunction(void* lpParam)
{
while (true)
{
std::cout << "Hello, World!" << std::endl;
Sleep(1000); // 休眠1秒
_endthreadex(0); // 结束线程
}
return 0;
}
int main()
{
// 创建线程
uintptr_t hThread = _beginthreadex(
NULL, // 默认安全属性
0, // 默认堆栈大小
ThreadFunction, // 线程函数
NULL, // 线程函数参数
0, // 默认创建标志
NULL); // 不需要线程ID
// 防止主线程立即退出
WaitForSingleObject((HANDLE)hThread, INFINITE);
// 关闭线程句柄
CloseHandle((HANDLE)hThread);
return 0;
}
获取线程的句柄
通过_beginthreadex
或者 CreateThread
函数创建线程成功之后,可以获取到新创建线程的句柄。除此之外,还可以通过GetCurrentThread
函数来获取当前正在运行的线程句柄。
HANDLE hThreadHandle = GetCurrentThread();
也可以调用GetCurrentThreadId
获取正在运行的线程ID。
DWORD id = GetCurrentThreadId();
暂停线程和重新运行线程
调用SuspendThread
函数和ResumeThread
函数可以暂停线程或者重新运行线程。同时在创建的时候,可以传递线程创建标志CREATE_SUSPENDED
,也可以使线程不用立即运行。
SuspendThread(hThreadHandle);
ResumeThread(hThreadHandle);
线程睡眠
通过调用Sleep
函数可以使线程睡眠,操作系统将不会给当前线程分配CPU时间。注意Sleep
函数的参数单位是毫秒。注意:如果给参数传递0,表示让操作系统调用另一个线程,强迫操作系统进行一次线程上下文切换,执行其他线程代码。
Sleep(1000);
获取线程上下文
每个线程都有自己的一个上下文,上下文记录和线程上一次执行的状态,包括寄存器状态、指令指针、堆栈信息、函数返回地址等等。我们可以通过GetThreadContext
函数来获取线程的上下文信息,当然一般情况下,这个上下文信息,应用程序很少会去关注。
BOOL ret = GetThreadContext(hThreadHandle, px);
线程优先级
Windows操作系统会给每个进程分配一个0-31的线程优先级代码,应用程序不必手动设置这个优先级代码。操作系统在给每个线程分配CPU时间的时候,会依据不同的优先级号,从低到高给不同的线程分配不同的CPU时间。如果没有特殊需要,正常情况下,我们创建的多线程使用默认的线程优先级即可。
Windows提供了六个优先级的类:空闲、低于正常、正常、高于正常、高和实时。其中,正常就是默认情况下的进程优先级。基本上99%的进程都应该使用这个优先级。
程序可以通过SetThreadPriority
来设置进程的优先级,原型如下:
BOOL SetThreadPriority(
HANDLE hThread,
int nPriority
);
其中第一个参数hThread
表示线程句柄,第二个参数nPriority
表示要调整的优先级,有7个选项。分别是
(1)、THREAD_PRIORITY_ABOVE_NORMAL :高于正常
(2)、THREAD_PRIORITY_BELOW_NORMAL: 低于正常
(3)、THREAD_PRIORITY_HIGHEST:最高
(4)、THREAD_PRIORITY_IDLE:空闲
(5)、THREAD_PRIORITY_LOWEST:最低
(6)、THREAD_PRIORITY_NORMAL:正常
(7)、THREAD_PRIORITY_TIME_CRITICAL:实时