[.net]线程基础
关于线程的诞生
早期的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]线程基础的更多相关文章
- Qt之线程基础
何为线程 线程与并行处理任务息息相关,就像进程一样.那么,线程与进程有什么区别呢?当你在电子表格上进行数据计算的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲.这是一个两个进程并行工作的例 ...
- Android多线程研究(1)——线程基础及源代码剖析
从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package ...
- JAVA与多线程开发(线程基础、继承Thread类来定义自己的线程、实现Runnable接口来解决单继承局限性、控制多线程程并发)
实现线程并发有两种方式:1)继承Thread类:2)实现Runnable接口. 线程基础 1)程序.进程.线程:并行.并发. 2)线程生命周期:创建状态(new一个线程对象).就绪状态(调用该对象的s ...
- 【windows核心编程】 第六章 线程基础
Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ① 一个是线程的内核 ...
- C#当中的多线程_线程基础
前言 最近工作不是很忙,想把买了很久了的<C#多线程编程实战>看完,所以索性把每一章的重点记录一下,方便以后回忆. 第1章 线程基础 1.创建一个线程 using System; usin ...
- Qt 线程基础(Thread Basics的翻译,线程的五种使用情况)
Qt 线程基础(QThread.QtConcurrent等) 转载自:http://blog.csdn.net/dbzhang800/article/details/6554104 昨晚看Qt的Man ...
- 线程基础(CLR via C#)
1.线程基础 1.1.线程职责 线程的职责是对CPU进行虚拟化.Windows 为每个进程豆提供了该进程专用的线程(功能相当于一个CPU).应用程序的代码进入死循环,于那个代码关联的进程会&quo ...
- Linux 系统应用编程——线程基础
传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文 ...
- python2 线程基础
1,感谢菜鸟教程, 线程基础:导入,创建函数,创建线和运行 import thread import time # 为线程定义一个函数 def print_time(threadName, delay ...
- 《CLR via C#》读书笔记 之 线程基础
第二十五章 线程基础 2014-06-28 25.1 Windows为什么要支持线程 25.2 线程开销 25.3 停止疯狂 25.6 CLR线程和Windows线程 25.7 使用专用线程执行异步的 ...
随机推荐
- 利用python计算windows全盘文件md5值的脚本
import hashlib import os import time import configparser import uuid def test_file_md5(file_path): t ...
- [leetcode]125. Valid Palindrome判断回文串
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignori ...
- php Pthread 多线程 (四) 共享内存
有些时候我们希望在多个线程中共享一些需要的数据,我们可以使用shmop扩展. <?php class Count extends Thread { private $name = ''; pub ...
- JFinal ORM和Hibernate简要对比
1.JFinal采用ActiveRecord实现数据库操作支持,较Hibernate开发效率提升六到十倍. 2.JFinal ActiveRecord较Hibernate学习成本低,一小时内能上手开发 ...
- 使用System.Net.Mail发送邮件
引用命名空间: using System.Net.Mail; /// <summary> /// 发送HTML邮件,有抄送和密送 /// 需要在Web.config文件中的system.n ...
- Region特征算子与形态学运算——第3讲
问题提出:求下图中楔形缺口到圆心的最短距离. [涉及知识点讲解] 一.Region特征算子 在图形窗口中用鼠标单击选中某个Region,然后点击菜单栏的“打开特征检测”图标,就可以看到当前Regi ...
- Ubuntu12.04下搭建Java环境
1.认识需要配置的环境变量 1). PATH: 作用是指定命令搜索路径,打开/etc/environment可以看到PATH变量的值,该变量包含了一系列的路径.那些路径都是一些经常使用的系统命令的目录 ...
- 201621123008 《Java程序设计》第五周学习总结
1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 关键词:接口,内部类. 1.2 尝试使用思维导图将这些关键词组织起来.注:思维导图一般不需要出现过多的字. 1.3 可选:使用常 ...
- js 和 css 中 不能使用 jsp 页面中一些 标签 和 java 代码等,应注意
js 和 css 中 不能使用 jsp 页面中一些 标签 和 java 代码等,应注意 如 ${ } <%%> 等
- 前端之css笔记2
1 属性选择器 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...