https://docs.microsoft.com/en-us/windows/desktop/procthread/multiple-threads
一个线程是一个进程内的可以被调度执行的实体。一个进程内所有的线程共享它的虚拟地址空间和系统资源。每个进程都开始于一个单线程,但它可以从任何它的线程中创建一个额外的线程。
创建线程
CreateThread 函数为一个进程创建一个新的线程。调用线程需要给新线程指定一个要执行的代码的起始地址。通常,起始地址是程序代码中定义的函数的名字。函数定义:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
一个进程可以有多个线程同时执行同样的函数。
新线程所执行的代码,尽量避免使用C 运行时函数(CRT),因为其中的很多函数都不是线程安全的,尤其是当你不使用多线程 CRT。如果你想在ThreadProc 中使用CRT ,使用_beginthreadex 函数。
传递本地变量的地址到新线程是不安全的,一旦创建者线程在新线程执行之前退出了,该指针将失效。因此,或者传递动态申请的内存的指针,或者让创建者线程等待所创建的线程的结束。
创建线程可以使用CreateThread 的参数来指定下面的设置:
1. 新创建的线程的句柄的安全属性。包括:继承标志,该句柄是否可以被子进程继承。安全描述符,在访问权限被授予之前,系统用来在所有的对线程句柄的使用上执行安全检查。
2. 初始栈大小。自动在进程的内存空间中申请。系统自动按需扩展栈的大小,并在线程推出后,关闭它。
3. 创建标志,使得可以创建一个处于暂停状态的线程。当创建暂停状态的线程,直到在线程上执行ResumeThread 函数,才会恢复新线程的执行。
可以通过调用CreateRemoteThread 函数创建线程。该函数被调试器用来被调试的进程的地址空间中创建一个线程。
线程栈大小
每个新线程或纤程接收到它自己的栈空间,包括预留的和初始提交的内存的大小。预留内存大小代表了虚拟内存中总的栈申请的大小。预留大小受限于地址空间范围。初始提交页面直到被引用才会对应物理页面。然而,它们确实从系统总的提交限制中移除了页面,该限制的值是:页面文件的大小+物理内存的大小。当需要的时候,系统从预留的栈内存中提交额外的页面,直到栈到达了预留大小-一个页面的大小(它作为一个保护页面,避免栈溢出),或系统处于低内存状态。
最好选择尽可能小的堆栈大小,并提交纤程或纤程运行所依赖的页面。为堆栈保留的每个页面都不能用于任何其他目的。
当它的线程退出,栈被释放。如果栈被其他的线程terminated,栈将不被释放。
默认的预留和初始提交的栈的大小在可执行文件的头部中保存。当thread 或 fiber 所要求的预留或提交数量满足不了,创建操作将失败。默认,链接时指定的栈大小是1MB。.def 文件中的STACKSIZE 声明可修改这个默认大小。操作系统将修正指定的大小,到最近的系统申请的对齐粒度(通常是64KB)。要检索当前系统的分配粒度,请使用GetSystemInfo 函数。
为了修改初始提交栈大小,在CreateThread、CreateRemoteThread、CreateFiber 函数中,使用dwStackSize 参数。该值被对齐最近的页面。通常,预留大小是可执行文件头中指定的,但当dwStackSize 指定的大小>= 默认预留大小,预留大小是这个新的提交大小,修正到最近的1MB 的倍数。
修改预留大小,CreateThread 或 CreateRemoteThread 的dwCreationFlags 指定STACK_SIZE_PARAM_IS_A_RESERVATION 并使用dwStackSize 参数。初始提交大小是PE 文件头中指定的默认大小。对于fiber,使用CreateFiberEx 中的dwStackReserveSize。提交大小在dwStackCommitSize 中指定了。
SetThreadStackGuarantee 函数,设置调用线程/fiber绑定的栈,在栈溢出异常产生的时候,的可使用的最小值。
线程句柄和标识符
默认,当通过CreateThread 和 CreateRemoteThread 创建一个新线程,函数将返回新线程的一个句柄,该句柄拥有全部权限,以及从属的安全访问检查,它可以在接收线程句柄做参数的任何函数使用。这个句柄可以被子进程继承,依赖于它被创建的时候的继承标志。该句柄可以通过DuplicateHandle 复制,这使得你可以使用访问权限的一个子集来创建一个线程句柄。直到该句柄被关闭,它都将是有效的,甚至在它所代表的线程被terminated。
CreateThread 和 CreateRemoteThread 也会返回一个系统内可以唯一标识该线程的ID ,即TID(线程ID)。线程可以调用GetCurrentThreadId 得到自己的TID。该ID 在该线程的生命周期内都是有效的。没有TID 为0.
如果你有一个TID,可以通过OpenThread 来得到一个线程句柄。OpenThread 可以指定要求的权限,以及是否可继承。
线程可以使用GetCurrentThread 来得到一个它自己的线程对象的伪句柄。这个伪句柄,仅仅对于该进程来说是有效的。为了得到真正的句柄,将该伪句柄传递给DuplicateHandle 函数。
为了枚举一个进程中的所有线程,使用Thread32First 和 Thread32Next 函数。
暂停线程执行
一个线程可以暂停和恢复另一个线程的执行。当一个线程被暂停,它不被调度处理器的时间。
如果一个线程被以暂停的状态创建,直到另一个线程执行ResumeThread 函数(以它的线程句柄为参数),它才开始执行。当你需要在线程执行之前设置它的状态,这将非常有 用。对于一次性同步来说,在创建线程的时候暂停它的执行将是十分有用的,因为这确保了被暂停的线程只有在调用了ResumeThread 的情况下才被调用。
SuspendThread 不可用于线程同步,因为它无法控制线程执行到哪个点的时候被暂停。这个函数,主要是设计给调试器使用的。
线程可通过调用Sleep 或 SleepEx 函数临时暂停它的执行,并指定暂停时间。这在线程响应用户交互的情况下尤为有用,因为它可以延迟执行足够长的时间以允许用户观察其操作的结果。在sleep 期间,线程不被调度给处理器时间。
SwitchToThread 函数与Sleep 和 SleepEx 函数类似,但是你不能指定间隔。SwitchToThread 允许线程放弃它的时间片。