核心编程6——线程

简介: <p><span style="font-size:18pt"><strong>Note 0:</strong></span><br><span style="font-size:18pt">了<span style="color:#ff0000">进程实际是由两个组件组成的:一个进程内核对象和一个地址空间.类似地,线程也由两个组件组成:</span></span><br><span st

Note 0:
进程实际是由两个组件组成的:一个进程内核对象和一个地址空间.类似地,线程也由两个组件组成:
一个是线程的内核对象,操作系统用它管理线程.内核对象还是系统用来存放线程统计信息的地方.
一个线程堆栈,用于维护线程执行时所需的所有函数参数和局部变量.
Note 1:
进程是有惰性的.进程从来不执行任何东西,它只是一个线程的容器.线程必然是在某个进程的上下文中创建的,而且会在这个进程内部"终其一生".这意味着线程要在其进程的地址空间内执行代码和处理数据.此外,这些线程还共享内核对象句柄,因为句柄表是针对每一个进程的,而不是针对每一个线程.相较于线程,进程所使用的系统资源更多.其原因在于地址空间.为一个进程创建一个虚拟的地址空间需要大量系统资源.系统中会发生大量的记录活动,而这需要用到大量内存.而且,由于.exe和.dll文件要加载到一个地址空间,所以还需要用到文件资源.另一方面,线程使用的系统资源要少得多.事实上,线程只有一个内核对象和一个堆栈;几乎不涉及记录活动,所以不需要占用多少内存.
Note 2:
线程描述了进程内部的一个执行路径.每次初始化进程时,系统都会创建一个主线程.对于用Microsoft C/C++编译器生成的应用程序,这个线程首先会执行C/C++运行库的启动代码,后者调用入口函数(_tmain或_tWinMain),并继续执行,直至入口函数返回C/C++运行库的启动代码,后者最终将调用ExitProcess.对于许多应用程序来说,这个主线程是应用程序惟一需要的线程.但是,进程也可以创建额外的线程来帮助它们完成自己的工作.

Note 3:
操作系统的Windows Indexing Services(Windows索引服务)创建了一个低优先级的线程.此线程定期醒来.并对硬盘上的特定区域的文件内容进行索引.Windows索引服务极大改进了性能.因为一旦成功建立索引.就不必在每次搜索时都打开、扫描和关闭硬盘上的每一个文件.配合这种索引服务.Microsoft Windows Vista提供了一套高级的搜索功能.

Note 4:
如果想创建一个或
多个辅助线程.只需让一个正在运行的线程调用CreateThread:
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD cbStackSize,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD dwCreateFlags,
PDWORD pdwThreadID);
调用CreateThread时.系统会创建一个线程内核对象.这个线程内核对象不是线程本身.而是一个较小的数据结构.操作系统用这个结构来管理线程.可以把线程内核对象想象为一个由线程统计信息构成的小型数据结构.这与进程和进程内核对象之间的关系是相同的
系统将进程地址空间的内存分配给线程堆栈使用.新线程在与负责创建的那个线程相同的进程上下文中运行.因此.新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他所有线程的堆栈.这样一来.同一个进程中的多个进程可以很容易地互相通信.
Note 5:
CreateThread函数是用于创建线程的Windows函数.不过.如果写的是C/C++代码.就绝对不要调用CreateThread.相反.正确的选择是使用Microsoft C++运行库函数_beginthreadex.如果使用的不是Microsoft C++编译器.你的编译器的提供商应该提供类似的函数来替代CreateThread.不管这个替代函数是什么.都必须使用它.

Note 6:
使用链接器的/STACK开关来控制线程堆栈使用多少地址空间.如下所示:
/STACK:[reserve] [,commit]
reserve参数用于设置系统将为线程堆栈预留多少地址空间.默认是1 MB(在Itanium芯片组上.默认大小为 4 MB).commit参数指定最初应为堆栈的保留区域提交多少物理存储空间.默认是1页.随着线程中的代码开始执行.需要的存储空间可能不止1页.如果线程溢出它的堆栈.会产生异常.(有关线程堆栈和堆栈溢出异常的详情.请参见第16章.有关常见异常处理的详情.请参见第23章.)系统将捕获这种异常.并将另一个页(或者为commit参数指定的任何大小)提交给保留空间.这样一来.线程堆栈就可以根据需要动态地增大.
Note 7:
保留空间的容量设定了堆栈空间的上限.这样才能捕获代码中的无穷递归bug.例如.假设你写了一个函数以递归方式调用其自身.而且这个函数存在一个bug.会导致无穷递归.每次此函数调用自身时.都会在内存堆栈上创建一个新的堆栈帧.如果系统没有设定堆栈空间的上限.这个递归调用的函数就永远不会终止调用自身.进程的所有地址空间都会被分配出去.大量物理存储会提交给堆栈.通过设置堆栈空间的上限.可以防止应用程序耗尽物理内存区域.而且还可以尽早察觉程序中的bug.
Note 8:
创建多个线程时.可以让它们使用同一个函数地址作为起点.这样做完全合法.而且非常有用.(可以传递了不同的pvParam值,来区分不同的线程所调用相同的函数.)

Note 9:
TerminateThread函数是异步的.也就是说.它告诉系统你想终止线程.但在函数返回时.并不保证线程已经终止了.如果需要确定线程已终止运行.还需要调用WaitForSingleObject或类似的函数.并向其传递线程的句柄.
Note 10:
如果通过返回或调用ExitThread函数的方式来终止一个线程的运行.该线程的堆栈也会被销毁.但是.如果使用的是TerminateThread.那么除非拥有此线程的进程终止运行.否则系统不会销毁这个线程的堆栈.Microsoft故意以这种方式来实现TerminateThread.否则.假如其他还在运行的线程要引用被"杀死"的那个线程的堆栈上的值.就会引起访问冲突.让被"杀死"的线程的堆栈保留在内存中.其他的线程就可以继续正常运行.
此外.动态链接库(DLL)通常会在线程终止运行时收到通知.不过.如果线程是用TerminateThread强行"杀死"的.则DLL不会收到这个通知.其结果是不能执行正常的清理工作.
Note 11:
线程终止运行时.会发生下面这些事情:
线程拥有的所有用户对象句柄会被释放.在Windows中.大多数对象都是由包含了"创建这些对象的线程"的进程拥有的.但是.一个线程有两个User对象:窗口(window)和挂钩(hook).一个线程终止运行时.系统会自动销毁由线程创建或安装的任何窗口.并卸载由线程创建或安装的任何挂钩.其他对象只有在拥有线程的进程终止时才被销毁.
线程的退出代码从STILL_ACTIVE变成传给ExitThread或TerminateThread的代码.
线程内核对象的状态变为signaled.
如果线程是进程中的最后一个活动线程.系统认为进程也终止了.
线程内核对象的使用计数递减1.

Note 19:
对CreateThread函数的一个调用导致系统创建一个线程内核对象.该对象最初的使用计数为2.(除非线程终止,而且从CreateThread返回的句柄关闭,否则线程内核对象不会被销毁.)该线程内核对象的其他属性也被初始化:暂停计数被设为1,退出代码被设为STILL_ACTIVE (0x103),而且对象被设为nonsignaled状态.
Note 20:
一旦创建了内核对象,系统就分配内存,供线程的堆栈使用.此内存是从进程的地址空间内分配的,因为线程没有自己的地址空间.然后,系统将两个值写入新线程堆栈的最上端.(线程堆栈始终是从高位内存地址向低位内存地址构建的.)写入线程堆栈的第一个值是传给CreateThread函数的pvParam参数的值.紧接在它下方的是传给CreateThread函数的pfnStartAddr值.
Note 21:
每个线程都有其自己的一组CPU寄存器,称为线程的上下文(context).上下文反映了当线程上一次执行时,线程的CPU寄存器的状态.线程的CPU寄存器全部保存在一个CONTEXT结构(在WinNT.h头文件中定义).CONTEXT结构本身保存在线程内核对象中.
Note 22:
当线程的内核对象被初始化的时候,CONTEXT结构的堆栈指针寄存器被设为pfnStartAddr在线程堆栈中的地址.而指令指针寄存器被设为RtlUserThreadStart函数(该函数未见于正式文档)的地址,此函数是NTDLL.dll模块导出的.
RtlUserThreadStart函数的基本用法如下:
VOID RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
__try {
ExitThread((pfnStartAddr)(pvParam));
}
__except(UnhandledExceptionFilter(GetExceptionInformation())) {
ExitProcess(GetExceptionCode());
}
// NOTE: We never get here.
}
Note 23:
线程完全初始化好之后,系统将检查CREATE_SUSPENDED标志是否传给CreateThread函数.如果此标记没有传递,系统将线程的暂停计数递增至0;随后,线程就可以调度给一个处理器去执行.然后,系统在实际的CPU寄存器中加载上一次在线程上下文中保存的值.现在,线程可以在其进程的地址空间中执行代码并处理数据了.
Note 24:
新线程执行RtlUserThreadStart函数的时候,将发生以下事情:
围绕你的线程函数,会设置一个结构化异常处理(Structured Exception Handling,SEH)帧.这样一来,线程执行期间所产生的任何异常都能得到系统的默认处理.
系统调用你的线程函数,把你传给CreateThread函数的pvParam参数传给它.
线程函数返回时,RtlUserThreadStart调用ExitThread,将你的线程函数的返回值传给它.线程内核对象的使用计数递减,而后线程停止执行.
如果线程产生了一个未被处理的异常,RtlUserThreadStart函数所设置的SEH帧会处理这个异常.通常,这意味着会向用户显示一个消息框,而且当用户关闭此消息框时,RtlUserThreadStart会调用ExitProcess来终止整个进程,而不只是终止有问题的线程.
Note 25:
当RtlUserThreadStart开始执行时,它会调用C/C++运行库的启动代码,后者初始化继而调用你的_tmain或_tWinMain函数.你的入口函数返回时,C/C++运行时启动代码会调用ExitProcess.所以对于C/C++应用程序来说,主线程永远不会返回到RtlUserThreadStart函数.
Note 26:
Visual Studio附带了4个原生的C/C++运行库,还有2个库面向Microsoft.NET的托管环境.注意,所有这些库都支持多线程开发:不再有单独的一个C/C++库专门针对单线程开发.下面对这些库进行了描述.
Microsoft Visual Studio附带的C/C++库
库名称      描述
LibCMt.lib   库的静态链接Release版本
LibCMtD.lib 库的静态链接Debug版本
MSVCRt.lib   导入库,用于动态链接MSVCR80.dll 库的Release版本. (这是新建项目时的默认库)
MSVCRtD.lib 导入库,用于动态链接MSVCR80D.dll库的Debug版本
MSVCMRt.lib 导入库,用于托管/原生代码混合
MSVCURt.lib 导入库,编译成百分之百纯MSIL代码

-------------------------------------------------------------------

相关文章
|
16天前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
8天前
|
安全 程序员 API
|
1天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
15 1
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
30 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
26 3
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1
|
10天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
17 3
|
12天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
10天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
10 2