• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2021-07-02 12:11 Aet 隐藏边栏 |   抢沙发  4 
文章评分 1 次,平均分 5.0

创建线程

概述

  1. 每个进程至少都有一个线程。
  2. 线程的组成
    1. 线程的内核对象,操作系统用它管理线程。
      系统还用内核对象来存放线程统计信息的地方。
    2. 线程栈,用于维护线程执行时所需的所有函数参数和局部变量。
  3. 进程从来不会执行任何东西,它是线程的容器。
    线程必然是在某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。
    也就是说,线程要在进程的地址空间内执行代码和处理数据。
  4. 假如一个进程中有超过两个以上的线程,那这些线程将共享同一个地址空间。可以执行一样的代码,可以处理相同的数据。
    另外,线程肯定是共享内核对象句柄的。因为句柄表是针对每一个进程的,而不是针对线程的。
  5. 所以,相比之下可以看出,进程占用系统资源更多,原因就是地址空间。
    为一个进程创建一个虚拟的地址空间需要大量的系统资源。
    系统中会发生大量的记录活动,需要用到大量的内存。而且,将.exe和.dll加载到地址空间,还需要用到文件资源。

什么时候创建线程

  1. 每次初始化进程时,系统都会创建一个主线程。
  2. 对于Microsoft C/C++编译器生成的应用程序,这个线程会首先执行C/C++运行库的代码,C/C++运行库的代码又会调用程序的入口点函数(如_tMain或_tWinMain),并继续执行。
    直到入口点函数返回C/C++运行库的启动代码,C/C++运行库的代码才会调用ExitProcess。
  3. 对于很多应用程序而言,这个主线程是应用程序唯一需要的线程。但是根据场景,将应用程序设计成多线程的,可以使应用程序易于扩展。

编写线程函数

  1. 线程函数可以执行我们希望它执行的任何任务。最后,线程函数会终止运行并返回。
    此时,线程终止运行了,用于线程栈的内存也会被释放,线程内核对象的使用计数也会递减。如果使用计数变成0了,线程内核对象会被销毁。
  2. 类似进程内核对象,线程内核对象的寿命至少可以达到与它们相关联的线程那样长。而线程内核对象的寿命是可能超过线程本身的寿命长度的。
  3. 默认情况下,主线程的入口点函数必须命名为main,wmain,WinMain或wWinMain。但是,我们可以用/ENTRY:链接器选项来指定另一个函数作为入口点函数。
    至于其他线程的函数可以任意命名。
  4. 主线程的入口点函数有字符串参数,所以它提供了ANSI/Unicode版本的给我们选择:main/wmain或WinMain/wWinMain。
    线程函数只有一个参数,而且其意义由我们来定义。
  5. 线程函数必须提供一个返回值,这个返回值会成为该线程的退出代码。
  6. 线程函数应该尽可能使用函数参数或局部变量。
    因为使用静态变量和全局变量时,多个线程可以同时访问这些变量,这会导致有可能破坏变量中保存的内容。

CreateThread

  1. 调用这个函数时,系统会创建一个线程内核对象。这个线程内核对象不是线程本身,而是一个较小的数据结构,操作系统用这个结构来管理线程。
  2. 系统从进程的地址空间中分配内存给线程栈使用。
  3. 新线程与负责创建的哪个线程在相同的进程上下文中运行。
    因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中的其他线程的栈。
    因此,同一个进程中的多个线程可以很容易地相互通信。

注意事项

  1. 这个函数是用于创建线程的Windows函数。
    如果写的是C/C++代码,就绝对不要调用这个函数来创建线程。正确的选择是使用Microsoft C++运行库的函数_beginthreadex。

参数一

  1. 一个指向SECURITY_ATTRIBUTES结构体的指针。
    如果是要使用线程内核对象的默认安全属性,则传入NULL。
  2. 如果是希望所有子进程都能继承到这个线程对象的句柄,那就必须指定一个SECURITY_ATTRIBUTES结构体,并将该结构体的bInheritHandle成员初始化为TRUE。

参数二

  1. 指定线程可以为其线程栈使用多少地址空间。
  2. 每个线程都有自己的线程栈。
    至于主线程,是CreateProcess函数开始一个进程的时候,会在内部调用CreateThread来初始化进程的主线程。
    主线程的这个参数,使用了保存在可执行文件内部的一个值。这个值可用链接器的/STACK开关来控制这个值。

  1. 随着线程中的代码开始执行,需要的存储空间可能不止1个页面,如果线程溢出它的栈,可能会发生异常。
    系统会捕获这种异常,并为已预定的空间区域调拨另一个页面。这样,线程栈就可以根据需要动态的增大。
  2. 如果传入非0值,函数会为线程栈预定空间并为之调拨所需的所有存储空间。
    如果传入的是0值,那么函数就会预定一个区域,并根据/STACK链接器开关指定的存储量来调拨存储器。
  3. 预留的地址空间的容量设定了栈空间的上限,这样才能捕获代码中的无穷递归bug。

参数三四

  1. 参数三指定了希望线程执行的线程函数的地址。
  2. 参数四是传给线程函数的参数。这个初始值可以是一个数值,也可以是一个指向数据结构的指针。
  3. 线程函数如果是不可重入函数,应该使用正确的线程同步技术。

可重入函数

  1. 可以在这个函数执行的任何时刻终端它,转入系统调度去执行另一段代码,而返回控制时不会出现什么错误。
  2. 也意味着这个函数除了使用自己栈上的变量外,不依赖任何环境,比如static变量。这样的函数就是pullcode可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈(多线程),所以互不干扰。

不可重入函数

  1. 由于函数使用了一项系统资源,比如全局变量区、中断向量表等,如果它被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下。

参数五

  1. 指定额外的标志来控制线程的创建。
  2. 如果值为0,线程创建之后立即就可以进行调度。
    如果值为CREATE_SUSPENDED,系统将创建并初始化线程,但是会暂停该线程的运行,这样就让它无法调度。

参数六

  1. 必须是一个DWORD的有效地址,CreateThread函数用它来存储系统分配给新线程的ID。
  2. 传NULL值的话,就是告诉函数我们对该线程的线程ID没有兴趣。

终止线程

方法

  • 线程函数返回(强烈推荐)
  • 线程通过调用ExitThread函数“杀死”自己。(避免使用)
  • 同一个进程或另一个进程中的其他线程调用TerminateThread函数(避免使用)
  • 包含线程的进程终止运行(避免使用)

线程函数返回

  1. 设计线程函数时,应该确保在我们希望线程终止运行时,就让它们返回。这是保证线程的所有资源被正确释放的唯一方法。
  2. 线程函数返回,可以确保的清理工作:
    1. 线程函数中创建的所有C++对象都通过其析构函数被正确销毁。
    2. 操作系统正确释放线程栈使用的内存。
    3. 操作系统把线程的退出代码设为线程函数的返回值。
    4. 系统递减线程的内核对象的使用计数。

ExitThread

  1. 强迫线程终止运行。

  1. 该函数终止线程的运行,并导致操作系统清理该线程使用的所有操作系统资源。
    但是,C/C++资源(如C++类对象)不会被销毁。

注意事项

  1. 这个函数是用于杀死线程的Windows函数。
    如果写的是C/C++代码,就绝对不要调用这个函数来终止线程。正确的选择是使用Microsoft C++运行库的函数_endthreadex。

TerminateThread

  1. 这个函数可用于杀死任何一个线程。

  1. 和ExitThread不同的是,ExitThread是杀死主调线程,而这个函数是可以杀死任何线程的。
  2. 线程的内核对象的使用计数会递减。
  3. 这个函数是异步的,在函数返回时,并不保证线程已经终止了。

参数一

  1. 要杀死的线程的句柄

参数二

  1. 退出代码

进程终止运行时

  1. 使用ExitProcess或TerminateProcess终止运行进程时,会终止运行在进程中的所有线程。

线程终止运行时

步骤

  1. 线程拥有的所有用户对象句柄会被释放。
    1. 在Windows中,大多数对象都是由包含了“创建这些对象的线程”的进程拥有的。但是一个线程有两个用户对象:窗口和钩子。
    2. 一个线程终止运行时,会自动销毁由线程创建或安装的任何窗口。
    3. 一个线程终止运行时,会自动卸载由线程创建或安装的钩子。
    4. 其他对象只有在拥有线程的进程终止时才被销毁。
  2. 线程的退出代码从STILL_ACTIVE变成传给ExitThread或TerminateThread的退出代码。
  3. 线程内核对象的状态变为触发状态。
  4. 如果线程是进程中的最后一个活动线程,系统认为进程也终止了。
  5. 线程内核对象的使用计数递减1。

其他

  1. 线程终止运行时,其关联的线程对象不会自动释放,除非这个对象所有未结束的引用都被关闭了
  2. 一旦线程不再运行了,系统中也没有其他线程再引用该线程的句柄了。
    其他线程可以使用GetExitCodeThread来检查hThread所标识的那个线程是否已经终止运行,如果已经终止运行,可判断其退出代码是多少。

  1. 退出代码的值通过pdwExitCode返回,如果调用这个函数的时候 ,线程还没终止,那么pdwExitCode的值将会是STILL_ACTIVE标识符(0x103)。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2021-11-20
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享