首页 > Python基础教程 >
-
C#教程之线程也疯狂------线程基础
前言
最近在做一个公司内部的分享,主要是关于线程处理方面,课程的目标是培训那些对线程理解和使用有所欠缺的开发人员,本篇文章主要介绍为什么引入线程的概念、CPU的发展趋势、线程开销、线程调度。
Windows为什么要支持线程
早期的操作系统是没有线程概念的,整个系统只运行一个执行线程,其中包含了系统代码和程序代码。只用一个执行线程的问题在于,长时间的运行任务会阻止其他任务执行,有些应用程序的bug会造成死循环,同样会造成整个机器停止工作,针对这个问题用户只好重启计算机。在这种迫切的需求下,微软计划构建一个新的OS来满足企业和个人的需要,新的OS必须健壮、可靠、易于伸缩和安全。
进程和线程的概念
进程实际是应用程序的实例要使用的资源的集合,每个进程都被赋予了一个虚拟的地址空间,确保在一个进程中使用的代码和数据无法由另一个进程访问。这样就确保了程序的健壮性,此外进程也无法访问OS的系统代码,使得操作系统更加稳定和安全。
但是对于CPU呢? 应用程序会死循环执行吗?如果安装上面所说,机器只有一个cpu,它会执行死循环,不能执行其他任何东西,虽然数据无法被破坏,而且更加安全但是系统仍然可能停止响应,微软针对这种问题,解决的方案就是线程。
线程职责:对CPU进行虚拟化,Windows为每个进程都提供了该进程专用的线程,如果应用程序的代码进入死循环,与那个代码关联的进程会冻结,但其他进程仍会继续执行。
线程开销
同一切虚拟化机制一样,线程有空间(内存)和时间(性能)上的开销:
1. 线程内核对象
通俗的讲就是线程的数据结构,主要包括:线程描述、线程上下文(CPU寄存器集合的内存块),不同的架构对应的线程上下文使用的字节也不同
2. 线程环境块(TEB)
TEB是在用户模式中分配和初始化的内存块,大约1个内存页4kb,主要包括:线程异常处理链首、线程本地存储数据以及由GDI和OpenGL图形使用的一些数据结构
3. 用户模式栈
存储传给方法的局部变量和实参。
4. 内核模式栈
验证用户模式栈中的参数的值。
5. DLL线程连接和线程分离通知
调用所有于线程有关操作(创建、终止等)的非托管DLL的DLLMain方法,并向方法传递ATTACH标志。
线程上下文
Windows任何时刻只将一个线程分配给CPU,每个线程只能运行一个时间片的长度,时间片到期,Windows就上下文切换到一另外一个线程,每次切换线程的操作:
1. 将CPU寄存器的值保存到当前正在运行的线程的内核对象内部的一个上下文结构
2. 从现有线程集合中选出一个线程提供调度
3. 将所选上下文结构中的值加载到CPU的寄存器中
上下文切换完成后,CPU执行所选的线程,直到它的时间片到期。上下文的切换对性能的影响非常大,而且切换所需的时间取决于CPU架构和速度,填充CPU缓存所需要的时间取决于操作系统中运行的程序、CPU缓存的大小以及其他各种因素,所以无法为每一次上下文的开销给出确定的值,所以要构建搞性能的应用程序和组件,尽量避免上下文切换。
线程调度和优先级
Windows之所以被成为抢占式多线程操作系统,是因为线程可在任何时间停止并调度另一个线程,每个线程都分配了从0到31的优先级,系统决定为CPU分配哪个线程时,首先检查优先级为31的线程,并通过轮流方式调度它们,如果优先级31的一个线程可以调度,就把它分配给CPU。在这个线程的时间片结束时,系统检查是否有另一个优先级31的线程可以运行。
只要存在可调度的优先级31的线程,系统就永远不会将<31的任何线程分配给CPU,这种情况称之为饥饿。
进程优先级类:Idle,Below Normal,Normal,Above Normal,High,Realtime. 默认为Normal
前台线程和后台线程
一个进程的所有前台线程停止运行时,CLR强制终止仍在运行的任何后台线程。
本篇主要是讲解线程的基础知识理论过多可能会枯燥,总的来说线程是非常宝贵的资源,必须省着使用,为了做到这一点,最好的方式就是使用CLR的线程池,线程池为你自动管理线程的创建和销毁。下篇会继续讲解,计算限制的异步操作,结合实际案例了解异步操作的优点。