线程的概念

线程的职责是对CPU进行虚拟化。

CPU为每个进程都提供了该进程专用的线程(功能相当于cpu),应用程序如果进入死循环,那么所处的进程会"冻结",但其他进程不会冻结,它们会继续执行!

线程的开销

因为是虚拟化CPU,所以也会有空间(内存耗用)和时间(执行性能)上的开销。

具体的开销:

  • 线程内核对象(thread kernel object)

    • 操作系统为创建的每个线程都会分配并初始化这种数据结构。数据结构包含一组对线程进行描述的属性,还包含线程的上下文,包含模拟CPU寄存器的集合的内存块。
  • 线程环境块(thread environment block,TEB)
    • TEB是在用户模式下分配和初始化的内存块。
    • TEB包含线程的异常处理链首。线程进入每个try块都在链首插入一个节点,线程退出try块从链中删除该节点。
    • TEB还包含线程的“线程本地存储”数据,以及由GDI和OpenGL图形使用的一些数据结构。
  • 用户模式栈(user-mode stack)
    • 用户模式栈存储传给方法的局部变量和实参。包含一个地址,指出当前方法返回,线程应该从什么地方接着执行。
    • Windows默认为每个线程的用户模式栈分配1MB的空间。 
  • 内核模式栈(kernel-mode stack)
    • 应用程序代码向操作系统中的内核模式函数传递实参时,还会使用内核模式栈。针对传给内核的任何实参,都会从用户模式栈复制到内核模式栈。
  • DLL线程连接(attach)和线程分离(detach)通知
    • 任何时候在进程中创建线程,都会调用进程中加载的所有非托管DLL的DLLMain方法,并向该方法传递DLL_THREAD_ATTACH标志。
    • 任何时候在进程中线程终止,都会调用进程中加载的所有非托管DLL的DLLMain方法,并向该方法传递DLL_THREAD_DETACH标志

从上面这些开销可以看出,创建和销毁一个线程的开销虽然没有进程那么大,但是也不小了。

甚至减少线程的数量还会提高垃圾回收的性能,因为垃圾回收时会挂起所有线程。

线程的切换也会有性能损失:

单CPU计算机一次只能做一件事情,所以所有的线程实际上是共享物理CPU的。多个CPU的计算机或者多核CPU,可以真正同时运行几个线程。然而单个线程还是只能在一个内核上运行。

任何时刻,一个CPU都只会被分配给一个线程。那个线程占用CPU一段时间后(叫做时间片),就会切换到另一个线程。(如果时间片结束后,Windows决定再次调用同一线程,那么不会执行线程切换)

线程切换大概每30毫秒进行一次。

每次线程切换时进行的操作:

  1. 将CPU寄存器的值保存到当前正在运行的线程的线程内核对象(前面提到过)内部的一个上下文结构。
  2. 从现有线程集合中选出一个线程供调度。如果该线程由另一个进程拥有,Windows在开始执行任何代码或者接触任何数据之前,还必须切换CPU“看见”的虚拟地址空间。
  3. 将切换到的新线程中,线程内核对象中的模拟寄存器的值加载到CPU的寄存器中

线程切换虽然消耗性能,但是却提供了一个健壮灵敏的操作系统。如果某线程进入死循环,不会影响其它线程。

线程在等待IO操作,会使线程进入等待状态,让线程在任何CPU上都不再调度,直到发生下一次输入事件。

使用专用线程执行异步的计算限制操作

以下介绍使用专用线程执行异步的计算限制操作,但是建议避免使用此技术,而用线程池来执行异步的计算限制操作。

如果执行的代码要求线程处于一种特定的状态,而这种状态对于线程池线程来说是非同寻常的,就可以考虑创建专用线程。

例如:

  • 线程需要以非普通线程优先级运行。而所有线程池都以普通优先级运行;虽然可以更改这一优先级,但不建议这么做。并且在不同的线程池操作之间,优先级的更改是无法持续的
  • 需要线程表现为一个前台线程,防止应用程序在线程任务结束前终止。线程池现场呢个始终为后台线程。如果CLR想终止进程,那么它们就完成不了任务。
  • 计算限制的任务需要长时间运行。线程池为了判断是否需要创建一个额外的线程,所采用的逻辑是比较复杂的。直接为长时间运行的任务创建专用线程,就可以避免这一问题。
  • 要启动线程,并可能调用Thread的Abort方法来提前终止它。

一个简单的使用专用线程执行异步操作的例子:

     static void Main(string[] args)
{
Thread 某线程 = new Thread(线程回调函数);
某线程.Start("hello");
Console.WriteLine("某线程运行开始");
某线程.Join();//join方法造成调用线程阻塞当前执行的任何代码,直到“某线程”销毁或者终止
Console.WriteLine("继续运行");
Console.Read();
}
private static void 线程回调函数(Object 状态参数) {
Thread.Sleep();
if (状态参数.GetType() == typeof(string))
{
Console.WriteLine("这是一个字符串");
}
else {
Console.WriteLine("未识别");
}
}

使用线程的理由

  • 可响应性

    • 在客户端GUI应用程序中,可以将一些工作交给线程进行,使GUI线程能灵敏响应用户输入。
  • 性能
    • 在多个CPU或多核CPU上使用多线程会提升性能,因为它可以真正意义上同时执行多件事情

线程调度和优先级

抢占式操作系统必须使用算法去判断什么时候调度哪些线程多长时间。

前面提到过,每个线程都包含一个线程内核对象,而内核对象中包含一个上下文结构,此结构中存储了线程上一次执行完毕后CPU寄存器的状态。

在一个时间片完后,windows会检查现存的所有线程内核对象,在这些对象中,只有那些没有正在等待什么的线程才适合调度。

Windows选择一个可调度的线程内核对象,并上下文切换到它。

然后线程开始执行代码,并在其进程的地址空间处理数据。然后过了一个时间片完后又循环执行以上操作。

Windows从系统启动开始便一直执行上下文切换,直到系统关闭为止。

之所以被称为抢占式操作系统,是因为线程可以在任何时间停止(被抢占)并调度另一个线程。

每个线程都分配了从0(最低)到31(最高)的优先级。系统决定为CPU分配哪个线程时,首先检查优先级为31的线程,并以一种轮流的方式调度它们。

只要还存在优先级高的可调度线程,那么就不会将优先级低的线程分配给CPU。这种情况称为饥饿。

系统启动时会创建一个特殊的零页线程,其优先级为0,而且是整个系统中唯一优先级为0的线程。在没有其它线程需要工作的时候,零页线程会将系统RAM的所有空闲页清零。

将优先级设为数字,实际操作中很难分配合理,于是微软给了一个更简单的方法。

在设计应用程序时,可以选择一个进程优先级类(可以选择Idle,Below Normal,Normal,Above Normal,High和Realtime),默认的Normal为最常见的优先级类。

事实上进程优先级类只是一个抽象的概念,Windows永远不会调度进程,只会调度线程。

在系统中什么都不做的时候运行的应用程序如屏保程序适合分配Idle优先级类。

而RealTime优先级优先级太高,甚至可能影响到操作系统任务,可能造成不能及时地处理键盘和鼠标输入。

而Windows还支持7个相对线程优先级(Idle,Lowest,Below Normal,Normal,Above Normal,Highest和Time-Critical),它们和进程优先级类一起确定最后的线程优先级。

最好是降低一个应用程序的优先级而不是提高另一个线程的优先级。高优先级的线程大多数时候应该使其保持为等待状态。

而我们可以通过设置Thread的Priority,向其传送ThreadPriority枚举类型定义的5个值之一:Lowest,Below Normal,Normal,Above Normal,Highest。

没有Idle和Time-Critical是因为CLR保留了。之前在垃圾回收那里提到的CLR的终结器线程以Time-Critical优先级运行。

如果应用程序以特殊的安全权限运行,可以使用System.Diagnostics命名空间中的Process和ProcessThread类,这两个类分别提供了进程和线程的Windows视图。

应用程序也可以使用AppDomain和Thread类,它们公开了AppDomain和线程的CLR视图。(这两个类不需要特殊的安全权限,只有部分操作需要)

前台线程和后台线程

CLR将线程分为前台线程和后台线程。

一个进程的所有前台线程停止运行时,CLR强制终止仍在运行的任何后台线程,并且不抛出异常。

static void Main(string[] args)
{
Thread 某线程 = new Thread(线程回调函数);
某线程.IsBackground = true;
某线程.Start("hello");
Console.WriteLine("某线程运行开始");
Console.WriteLine("继续运行");
}
private static void 线程回调函数(Object 状态参数) {
Thread.Sleep();
if (状态参数.GetType() == typeof(string))
{
Console.WriteLine("这是一个字符串");
}
else {
Console.WriteLine("未识别");
}
}

如果某线程为前台线程,那么应用程序在10秒后才终止,如果是后台线程,那么应用程序立即终止。

原因是如果是前台线程,那么在运行完Main函数后,还需要这个前台线程结束才终止应用程序,而如果转为后台线程,那么就会在所有前台线程终止后立马终止。

通过Thread对象来显示创建线程的都是前台线程,但是可以通过IsBackgroun属性来切换,而线程池都是后台线程。

【C#进阶系列】25 线程基础的更多相关文章

  1. [CLR via C#]25. 线程基础

    一.Windows为什么要支持线程 Microsoft设计OS内核时,他们决定在一个进程(process)中运行应用程序的每个实例.进程不过是应用程序的一个实例要使用的资源的一个集合.每个进程都赋予了 ...

  2. 25线程基础-CLR

    由CLR via C#(第三版) ,摘抄记录... 1.线程是CPU的虚拟化,windows为每个进程提供专用线程(CPU)2.线程开销:内存和时间. 线程内核对象—OS为系统中创建的每个线程都分配并 ...

  3. Python系列之 - 线程基础

    一.什么是线程 线程:顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才 ...

  4. C#进阶系列 ---- 《CLR via C#》

      [C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...

  5. C#多线程编程系列(二)- 线程基础

    目录 C#多线程编程系列(二)- 线程基础 1.1 简介 1.2 创建线程 1.3 暂停线程 1.4 线程等待 1.5 终止线程 1.6 检测线程状态 1.7 线程优先级 1.8 前台线程和后台线程 ...

  6. C#进阶系列——WebApi 身份认证解决方案:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想 ...

  7. #进阶系列——WebApi 身份认证解决方案:Basic基础认证

    阅读目录 一.为什么需要身份认证 二.Basic基础认证的原理解析 1.常见的认证方式 2.Basic基础认证原理 三.Basic基础认证的代码示例 1.登录过程 2./Home/Index主界面 3 ...

  8. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  9. Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量

    Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程:    1.线程是一堆指令,是操作系统调度 ...

随机推荐

  1. iOS开发系列--C语言之预处理

    概述 大家都知道一个C程序的运行包括编译和链接两个阶段,其实在编译之前预处理器首先要进行预处理操作,将处理完产生的一个新的源文件进行编译.由于预处理指令是在编译之前就进行了,因此很多时候它要比在程序运 ...

  2. 关于新书《修炼之道:.NET开发要点精讲》的各种说明

    索引 新书介绍 新书封面 新书目录 试读章节 原稿试读 网购地址 规格参数 反馈方式 一些感谢 附加说明 1.新书介绍 从2013年年底到2014年9月,历时将近10个月,这本书终于看到了“出版发行” ...

  3. Google分布式构建软件之三:分布式执行构建步骤

    注:本文英文原文在google开发者工具组的博客上[需要FQ],以下是我的翻译,欢迎转载,但请尊重作者版权,注名原文地址. 之前两篇文章分别介绍了Google 分布式软件构建系统Blaze相关的为了提 ...

  4. Azure PowerShell (10) 使用PowerShell导出订阅下所有的Azure VM和Cloud Service的高可用情况

    <Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China服务. 该脚本下载地址在http://files.cnblogs.co ...

  5. Ubuntu 16 安装ElasticSearch

    首先安装Java,参见博客:http://www.cnblogs.com/1zhk/p/6056406.html 下载ElasticSearch安装包 curl -L -O https://artif ...

  6. Python virtualenv with Sublime Text 3

    背景介绍 最近喜欢上了Sublime编辑器,刚开始学着用.不过对我这个python狂人来讲,首要问题是需要sublime支持python virtualenv包的导入.所以我就找了google最后找到 ...

  7. Transactional Replication2:在Subscriber中,主键列是只读的

    在使用Transactional Replication时,Subscriber 被认为是“Read-Only”的 , All data at the Subscriber is “read-only ...

  8. MindManger 2016 64位 破解版

    下载地址:http://pan.baidu.com/s/1mi7xkIO 如果失效请去 “毒逆天吧” 发个求助帖即可就有人补上

  9. C指针(一)

    原文链接:http://www.orlion.ga/916/ 一.指针的基本操作 例: int i; int *pi = &i; char c; char *pc = &c; &quo ...

  10. 取消vs2013在solution中单击打开文件的功能

    2013用了一段时间,不错,就是单击会打开文件,有点恼人(因人而异吧).解决方案: 取消红色框框里面的那个checkbox就ok了. 来自为知笔记(Wiz)