热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

线程漫谈——线程基础

本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。进程与线程理解线程是至关重要的,每个进程至少有一个线程,进程是线程的容器,线程才是真正的执行体,线程必

本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。

进程与线程

理解线程是至关重要的,每个进程至少有一个线程,进程是线程的容器,线程才是真正的执行体,线程必然在某个进程的上下文中运行。进程拥有惰性,如果进程中所有的线程都已结束,那么进程也就没有存在的必要了。

一个进程由如下两部分组成:1、一个进程地址空间;2、一个进程内核对象

一个线程由如下两部分组成:1、一个线程栈;2、一个线程内核对象

线程的开销要比进程少很多,所以在解决编程问题的时候尽量考虑在当前进程中创建线程而不是创建新的进程。然而,线程的切换需要消耗一定数量的CPU资源,因此,也不是说可以毫无顾忌的使用线程来处理问题。

线程生命周期

线程的创建

系统创建一个线程内核对象;

系统在当前进程的中预订一块线程栈空间,并调拨一些物理内存;

线程终止运行

释放线程所拥有的所有用户对象(不太理解)

线程退出代码从STILL_ACTIVE变成真正的退出代码

线程内核对象变为触发状态

如果线程是进程中最后一个活动线程,进程将终止

线程内核对象的使用计数减1

线程的创建和终止方法

无论你使用什么编程语言,什么类库,在Windows平台下最终创建线程都应该有下面的API

?
1
2
3
4
5
6
7
8
HANDLE WINAPI CreateThread(
  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全描述符,一般传入NULL,但其中的bInheritHandle标志位说明线程内核对象是否允许子进程继承
  __in       SIZE_T dwStackSize,//线程栈初始化大小,该值可以传入0,系统会从/STACK链接选项和此值两个中选一个较大的
  __in       LPTHREAD_START_ROUTINE lpStartAddress,//线程执行的初始函数的地址
  __in_opt   LPVOID lpParameter,//传入初始函数的参数
  __in       DWORD dwCreationFlags,//指定线程是否能被立即调度(即是否立即执行),如果为CREATE_SUSPENDED,系统会在初始化后暂停线程的运行
  __out_opt  LPDWORD lpThreadId//线程ID,可以传入NULL
);

需要说明的是线程初始函数总是拥有如下函数签名:

?
1
2
3
DWORD WINAPI ThreadProc(
  __in  LPVOID lpParameter
);

该函数返回创建好的线程内核对象的句柄。除非该句柄值将用作他用,否则应该立即调用CloseHandle来关闭句柄,当然关闭句柄不会终止线程的运行,但可以保证线程在退出后即使的释放线程内核对象。

终止线程的方式有线程初始函数返回、线程自己调用ExitThread终止自己、外部线程调用TerminateThread、包含线程的进程终止运行。其中除了第一种方法,其他都是不推荐的方式,因为线程不正常退出不能保证资源的正确释放

使用ExitThread还能保证系统销毁线程的堆栈,但TerminateThread将无法做到,直到进程终止;

线程的初始化内幕

image

CreateThread导致系统创建一个线程内核对象,该对象的初始引用计数为2。线程正常退出将递减一次,关闭线程句柄将递减一次,引用计数为0时,操作系统会释放改内核对象。暂停计数设置为1,退出代码设置为STILL_ACTIVE,对象被设置为未触发状态。

系统从进程地址空间中分配线程栈,并在高位写入pvParam和pfnStratAddr。

每个线程都有其自己的一组CPU寄存器,称为线程上下文。上下文反映了线程上一次执行时,线程的CPU寄存器状态。当线程被重新调度时,保存在内核对象中的上下文将回写到CPU寄存器,以恢复线程的最后状态,这个过程称为“上下文切换”。其中最为重要的两个寄存器是堆栈寄存器(SP)和指令寄存器(IP),SP指向pfnStartAddr在堆栈中的地址,IP指向RtlUserThreadStart函数(NTDLL.dll导入)。Windows实际上提供了一个描述线程上下文的结构CONTEXT,并且提供了如下两个函数获得和设置上下文:

?
1
2
3
4
5
6
7
8
9
BOOL WINAPI GetThreadContext(
  __in     HANDLE hThread,
  __inout  LPCONTEXT lpContext
);
 
BOOL WINAPI SetThreadContext(
  __in  HANDLE hThread,
  __in  const CONTEXT *lpContext
);

CONTEXT结构可能是Windows平台上唯一一个跟CPU有关的结构,所以如果要设置该结构可能需要考虑不同CPU的情况,而且如果设置不当,很有可能导致灾难性的后果。

线程完全初始化好之后,系统将检查CreateThread函数中的dwCreationFlags,如果此标记不是CREATE_SUSPENDED,系统将把挂起计数递减至0,以便处理器调度该线程。

Microsoft C/C++运行库注意事项

Visual Studio附带了4个C/C++运行库用于开发,还有两个面向.NET托管环境。现在所有的库都支持多线程开发,已经没有专门针对单线程开发的运行库(C\C++运行库的出现早于多任务操作系统,因此当时有支持单线程的运行库):

库名称 描述
LibCMt.lib 库的静态链接发行版本

LibCMtD.lib
库的静态链接调试版本
MSVCRt.lib 导入库,用于动态链接MSVCRxxx.dll库的发行版本
MSVCRtD.lib 导入库,用于动态链接MSVCRxxxD.dll库的调试版本(默认)
MSVCMRt.lib 导入库,用户托管/本机代码混合
MSVCURt.lib 导入库,编译成百分之百的纯MSIL代码

始终应该用运行库的_beginthreadex来创建线程,以及用_endthreadex来代替ExitThread。

线程的挂起、恢复和睡眠

如上文所讨论的,线程在创建的时候可以设置是否挂起。我们可以通过如下两个函数来挂起和恢复线程

?
1
2
3
4
5
6
7
DWORD WINAPI SuspendThread(
  __in  HANDLE hThread
);
 
DWORD WINAPI ResumeThread(
  __in  HANDLE hThread
);

调用SuspendThread将递增线程内核对象的挂起计数,可以挂起多次,对应的也可以恢复多次。另外,没有完美的“挂起进程”的函数,因为唯一的方法就是遍历并挂起进程中的所有线程,然而在遍历的过程中如果有新的线程创建了呢,亦或是刚遍历的线程销毁了呢。因此试图“挂起进程”要十分小心,尽量避免。

使用下面的函数指定挂起当前线程一段时间

?
1
2
3
VOID WINAPI Sleep(
  __in  DWORD dwMilliseconds
);
 
分类: C\C++, Windows Programming
标签: Windows, 线程
推荐阅读
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
author-avatar
学渣小小柱
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有