关于线程的诞生

  早期的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. php反射机制学习

    PHP 5 具有完整的反射 API,可以通过反射机制来获取类,接口,函数的详细信息.例如可以通过反射api的成员属性,成员方法,命名空间的名称,检测某个类是否为抽象类等操作.(欢迎指点) 一般用途是在 ...

  2. 基本控件设置边角图片 drawableleft

    btn.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_galley_comment, 0, 0,0); 四个参数分别是左上右下四个方向 ...

  3. 【转】Repository 返回 IQueryable?还是 IEnumerable?

    这是一个很有意思的问题,我们一步一步来探讨,首先需要明确两个概念(来自 MSDN): IQueryable:提供对未指定数据类型的特定数据源的查询进行计算的功能. IEnumerable:公开枚举数, ...

  4. Windows下误删资料的恢复

    只要三步,就能找回你删掉并清空回收站的东西 : 1.打开“运行”消息框,然后输入regedit (打开注册表) 2.依次展开:HEKEY——LOCAL——MACHIME/SOFTWARE/micros ...

  5. ldd "symbol lookup error"问题解决

    http://www.linuxquestions.org/questions/slackware-14/symbol-lookup-error-usr-lib-libgtk-x11-2-0-so-0 ...

  6. 关于MySQL在内网中使用另一台机器访问的问题

    要在内网中访问另一台机器的MySQL数据库,需要两步操作 一是把运行MySQL的机器的3306端口打开,最好是能限制访问IP保证安全性. 二是更改MySQL账户的访问权限.MySQL的root账户默认 ...

  7. [ Laravel 5.5 文档 ] 底层原理 —— 一次 Laravel 请求的生命周期

     Posted on 2018年3月5日 by  学院君 简介 当我们使用现实世界中的任何工具时,如果理解了该工具的工作原理,那么用起来就会得心应手,应用开发也是如此.当你理解了开发工具如何工作,用起 ...

  8. 05 Maven 生命周期和插件

    Maven 生命周期和插件 除了坐标.依赖以及仓库之外, Maven 另外两个核心概念是生命周期和插件.在有关 Maven 的日常使用中,命令行的输入往往就对应了生命周期,如 mvn package ...

  9. jsp 页面 摘要, 要截取字符串 ,当时 字符串中包含 html标签,截取后无法显示

    如题: 处理办法: 1.  使用struts标签 <s:property  value ="#text.replaceAll('<[^>]+>','').substr ...

  10. 深入浅析JavaScript中with语句的理解

    JavaScript 有个 with 关键字, with 语句的原本用意是为逐级的对象访问提供命名空间式的速写方式. 也就是在指定的代码区域, 直接通过节点名称调用对象. with语句的作用是暂时改变 ...