关于线程的诞生

  早期的16位Windows只有一个执行线程,在执行各种程序时,如果这个线程运行出现了问题,就会“冻结”整个系统,使得系统处于未响应状态。这是一件多么尴尬的事儿,无论是用户还是微软自己,都不能长时间的忍受这种状况!不过,在那个时代,能有一台电脑,能使用Windows本身就是一件极其奢侈和有乐趣的事儿,也许用户还是能接受这种状况的。不过随着芯片技术的发展,微软是绝对不能安于现状的,所以必须设计一个健壮、可靠、易于伸缩和安全的系统,以便于和新的芯片搭配,所以微软推出了Windows NT,第一次搭载了全新的内核,后续的Windows版本,都是基于这个新的内核。那么这个内核和线程有什么关系呢?在新的系统内核中,操作系统管理多个进程,每个进程又管理这多个线程。

  在进程中运行每个应用程序的实例。而进程是应用程序实例要使用的资源的集合,每一个进程都有一个独立的虚拟地址空间,所以进程之间不能访问各自的代码和数据,当然也不能访问OS本身的数据。这就保证了系统运行的安全。可是,早期计算机只有一个CPU,如果CPU执行陷入死循环,那么系统任然会停止响应。为了解决这个问题,微软拿出了一个解决方案——线程。让每个进程拥有各自的线程。线程的职责就是对CPU进行虚拟化,对于应用程序来说,实际上一个线程相当于一个CPU,把所有的代码执行任务交给线程,如果某个线程执行出错,那么只有与这个线程关联的进程会挂掉,其他的进程不受影响,在OS的控制下,它们继续很好的执行!

线程究竟包含什么?

  现在才是干货。在Windows中,每一个线程都会有这么几个要素。

  ·线程内核对象(Thread Kernel Object):这是线程非常重要的一个数据结构,它包含了一些对线程描述的属性,而且包含线程上下文(thead context)。上下文表示的是线程的执行信息,这些信息分别被保存在CPU的寄存器上。线程切换的时候,首先会保存当前线程的上下文信息,然后切换到另一个线程,最后恢复另外一个线程的上下文信息到实际的CPU寄存器中。

  ·线程环境块(thread environment block,TEB):当线程执行进入一个try块(try{}catch{}用于捕获异常)的时候,就会在TEB中记录一个节点。与此同时,TEB还包含了一些图形接口相关的数据结构。

  ·用户模式栈(user-mode stack):线程执行中的局部变量,方法调用参数都被存储在这儿。而且还指出了在当前线程执行的方法结束后,线程应该回到什么地方接着执行,类似中断过程。关于用户模式栈,是一个线程消耗系统内存资源的大户。为什么这么说呢?之前提到的 线程内核对象 和 TEB 它们的大小只有几kb,最小都不足1kb,但是Windows 给 用户模式栈 的初始大小就是1MB,而且随着实际需要,系统会调拨更多的物理内存给它。也许你会觉着不就1MB嘛,我内存是8GB的。那你就年轻了,然而就在笔者写这篇笔记的时候,我的Windows 10 系统有121个进程,有2100个线程。粗略计算,至少有2100x1MB的内存空间用于创建线程了,也就是2GB。

  ·内核模式栈(kernal-mode stack):应用程序调用内核模式函数传参时使用。为什么会有内核模式栈出现呢?其实还是为了安全,用户调用内核模式函数的参数会被从用户模式栈复制到内核模式栈,然后它的功能就和用户模式栈则差不多了。简单理解就是内核模式栈服务于系统内核方法,用户模式站服务于用户自己的方法。

  ·DLL线程连接和线程分离通知:再Windows中有一个策略,再进程中创建线程时,会遍历当前进程加载的所有非托管DLL的DllMain方法。然后传递一个attach标记。所以创建一个线程会有一定的性能损耗。现在一个常用的的应用,都会加载N多的Dll。例如Vs2015,它在运行的时候至少会加载三四百个DLL,它们中有许多是非托管的Dll,遍历用这些Dll的入口函数也是一件不小的工作。不过值得高兴的一点是Windows 提供了一个Win32 方法DisableThreadLibraryCalls,非托管的Dll调用这个方法,就可以不去理会线程连接的通知,但是,许多的非托管Dll的程序员都不知道这个函数的存在,这就很尴尬了!

  以上五点就是一个线程会包含的基本要素了。线程很好,因为它可以让我们的程序在表面上并行运行,但是滥用线程可就不好了!物极必反。创建一个线程会对系统的运行造成一定的性能损耗,虽然这个损耗可能很小很小,但是会由量变到质变。在一个进程中,每一时刻都只能有一个确定的线程执行,然而N多的线程是通过不断切换执行的,Windows大概每30ms就回执行一次线程切换,正如前面所说的,在线程切换的时候,要经历一个保存线程上下文—切换线程—加载线程上下文的过程。如果线程切换的频率很低,必然没有什么问题,但是在高频率的线程切换中,会影响系统性能。Cpu在读取数据时,从高速缓存cache中读取的速度远大于内存ram,而执行线程上下文切换,会导致原本在cache中的数据丢失,需要重新从ram中读取数据。这就是影响性能的地方。
  目前为止,在一个单核cpu上,确定时刻只能由一个线程执行。直到多CPU计算机、多核CPU的出现,才能真正实现在同一时刻,有多个线程同时执行。而多CPU的计算机由于成本,功耗等各种原因,使用的并不多。真正得到普及的是多核CPU。试想,在多核CPU上,最完美的状态就是CPU有多少个核心,就创建多少个线程,这样就不会有线程切换,性能得到了保障。实际在一个Windows系统中,同时会有上千个线程。美好的理想就这么残忍的被现实打败了!但是在多核CPU普及的今天,我们的确从其多核心的计算中受益。

  线程切换的时候,需要考虑一个问题:在一个进程中,有许多的线程,如何选择切换对象呢?在Windows中,引入了线程优先级的概念。Windows 将这种优先级量化为32个级别,数值越大,优先级越高,数值从0到31。切换时会优先切换高优先级的线程。但,问题又出现了,开发者并不能很好的掌握线程的优先级应该设置为多少。为了解决这个问题,Windows又引入了一个优先级抽象层,有两个概念“进程优先级类“和“相对线程优先级”。进程优先级类针对每一个进程,分为idle,below normal,normal,above normal,high,realtime六个级别,而相对线程优先级则分为idle,lower,below normal,normal,above normal,highest,time-critical七个级别,他们的不同组合对应了32个数字优先级。具体看图

  idle lowest below normal normal above normal highest time-critical
idle 1 2 3 4 5 6 15
below normal 1 4 5 6 7 8 15
normal 1 6 7 8 9 10 15
above normal 1 8 9 10 11 12 15
high 1 11 12 13 14 15 15
realtime 16 22 23 24 25 26 31

.Net中的线程
  前后台线程:在.net中创建的线程分为前后台线程,前台线程如果没有结束,则整个应用程序就不能结束,依然会暂留在进程中,直到前台线程结束。而与之对应的后台线程则不同,一旦应用程序终止,后台线程随即终止,无论是否完成。我们可以通过System.Threading这个命名空间下的类来操作线程。而通过直接实例化一个Thead对象来创建的线程,默认是一个前台线程,可以通过IsBackground属性将其设置为后台线程。但是讲道理,平时使用的线程中,一般都是后台线程,通过线程池创建的线程都是后台线程。

  多线程编程无论在什么编程语言中,都是一个重要的话题,而以上这些,只是.net中多线程编程的开始!

声明:笔记中内容均参考自Jeffrey Richter著作的《CLR Via C#》,个人也很看同作者的一些观点,像大神致敬!

[.net]线程基础的更多相关文章

  1. Qt之线程基础

    何为线程 线程与并行处理任务息息相关,就像进程一样.那么,线程与进程有什么区别呢?当你在电子表格上进行数据计算的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲.这是一个两个进程并行工作的例 ...

  2. Android多线程研究(1)——线程基础及源代码剖析

    从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package ...

  3. JAVA与多线程开发(线程基础、继承Thread类来定义自己的线程、实现Runnable接口来解决单继承局限性、控制多线程程并发)

    实现线程并发有两种方式:1)继承Thread类:2)实现Runnable接口. 线程基础 1)程序.进程.线程:并行.并发. 2)线程生命周期:创建状态(new一个线程对象).就绪状态(调用该对象的s ...

  4. 【windows核心编程】 第六章 线程基础

    Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ①    一个是线程的内核 ...

  5. C#当中的多线程_线程基础

    前言 最近工作不是很忙,想把买了很久了的<C#多线程编程实战>看完,所以索性把每一章的重点记录一下,方便以后回忆. 第1章 线程基础 1.创建一个线程 using System; usin ...

  6. Qt 线程基础(Thread Basics的翻译,线程的五种使用情况)

    Qt 线程基础(QThread.QtConcurrent等) 转载自:http://blog.csdn.net/dbzhang800/article/details/6554104 昨晚看Qt的Man ...

  7. 线程基础(CLR via C#)

    1.线程基础  1.1.线程职责  线程的职责是对CPU进行虚拟化.Windows 为每个进程豆提供了该进程专用的线程(功能相当于一个CPU).应用程序的代码进入死循环,于那个代码关联的进程会&quo ...

  8. Linux 系统应用编程——线程基础

    传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文 ...

  9. python2 线程基础

    1,感谢菜鸟教程, 线程基础:导入,创建函数,创建线和运行 import thread import time # 为线程定义一个函数 def print_time(threadName, delay ...

  10. 《CLR via C#》读书笔记 之 线程基础

    第二十五章 线程基础 2014-06-28 25.1 Windows为什么要支持线程 25.2 线程开销 25.3 停止疯狂 25.6 CLR线程和Windows线程 25.7 使用专用线程执行异步的 ...

随机推荐

  1. oracle基本查询入门(二) 子查询

    一.子查询语法 SELECT select_list FROM table WHERE expr operator (SELECT select_list FROM table); 子查询在主查询之前 ...

  2. discuz回贴通知插件实现-插件后台管理配置

    1.登出discuz后台,再次设计插件 2.使用变量    

  3. 源码安装php时出现configure: error: xml2-config not found. Please check your libxml2 installation

    1.检查是否安装了libxml 包 > rpm -qa|grep libxml2 2.如果没有则安装 > yum install libxml2 > yum install libx ...

  4. Mina入门:mina版之HelloWorld[z]

    Mina入门:mina版之HelloWorld [z] 一,前言: 在完成上篇文章<Mina入门:Java NIO框架Mina.Netty.Grizzly简介与对比>之后,我们现在可以正式 ...

  5. 一句话引发的思考 - synchronized/super

    https://blog.csdn.net/cool__wang/article/details/52459380#commentBox

  6. 欲哭无泪的p-value = 0.051 | 做几次重复能得到较低的p-value

    欲哭无泪的p-value = 0.051 | 做几次重复能得到较低的p-value 已有 1469 次阅读 2017-12-15 14:12 |个人分类:RNA-seq|系统分类:科普集锦|关键词:R ...

  7. Ubuntu12.04下搭建Java环境

    1.认识需要配置的环境变量 1). PATH: 作用是指定命令搜索路径,打开/etc/environment可以看到PATH变量的值,该变量包含了一系列的路径.那些路径都是一些经常使用的系统命令的目录 ...

  8. 有符号无符号bit转换

    int main(){ unsigned short i = 65434; short p = i; printf("%d", p); int sp; scanf_s(" ...

  9. 设计规范VS设计创造力,谁更胜一筹?

    设计规范和设计创造力哪个更重要?这是一个颇具争议性的话题.如果是3年前问我这个问题我会毫不犹豫的选择设计创造力,毫无疑问,一个好的设计创造力真的是可以让人像打了鸡血一样疯狂. 原来在上大学的时候,我就 ...

  10. MySQL 组合查询 concat

      concat( pms_user.f_pu_name, '(' , pms_user.f_pu_realName,')') as userIds