多线程,创建线程、线程栈大小、线程句柄和标识符(TID)、暂停线程执行

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 函数创建线程。该函数被调试器用来被调试的进程的地址空间中创建一个线程。

扫描二维码关注公众号,回复: 10526520 查看本文章

线程栈大小

每个新线程或纤程接收到它自己的栈空间,包括预留的和初始提交的内存的大小。预留内存大小代表了虚拟内存中总的栈申请的大小。预留大小受限于地址空间范围。初始提交页面直到被引用才会对应物理页面。然而,它们确实从系统总的提交限制中移除了页面,该限制的值是:页面文件的大小+物理内存的大小。当需要的时候,系统从预留的栈内存中提交额外的页面,直到栈到达了预留大小-一个页面的大小(它作为一个保护页面,避免栈溢出),或系统处于低内存状态。

最好选择尽可能小的堆栈大小,并提交纤程或纤程运行所依赖的页面。为堆栈保留的每个页面都不能用于任何其他目的。

当它的线程退出,栈被释放。如果栈被其他的线程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 允许线程放弃它的时间片。

发布了93 篇原创文章 · 获赞 13 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_18218335/article/details/84206222