.NET 线程基础的使用介绍

线程作用及开销

  早期计算机一次只能运行一个程序,长时间执行程序容易出现计算机“瘫痪”的问题,如果程序进入死循环则只能重启系统。即使计算机不崩溃,也难免让用户崩溃。为了解决这个问题,操作系统设计者设计出了进程的概念,使得每个应用程序运行在一个虚拟的内存空间中。进程中又包含多个线程,CPU则根据操作系统调度执行每个进程中的线程任务。通过线程这种对CPU的虚拟化管理方式,操作系统形成了多任务执行的机制。但与一切虚拟化机制一样,线程会产生空间和时间的开销。这其中的开销包括:

1、线程内核对象。该数据结构中包含线程上下文。Windows在x86架构CPU上为每个线程内核对象分配的空间大约为700字节,x64和IA64架构CPU分别为大约1240字节和2500字节。

2、线程环境块。占用一个内存页,包含线程的异常处理链首。当线程进入try块时,在链首插入一个节点,在线程对出退出try块时,删除该节点。另外线程环境块中还包括一些其他的本地储蓄数据。

3、用户模式栈。用户存储传给方法的局部变量和实参;还包含一个地址,指出当方法返回时,线程应该从什么地方开始接着执行。默认情况下每个线程的用户模式栈分配1MB内存。

4、内核模式栈。记录用户程序调用内核模式函数时函数的实参。32位系统分配12KB内存,64位系统则分配24KB。

5、DLL线程连接和线程分离通知。进程中每创建和终止一个线程时,都会调用进程中加载的所有DLL的DllMain方法。

6、上下文切换。对单CPU计算机来说,操作系统每次只将一个线程分配给CPU执行,执行完后将线程上下文数据记录下来保存在线程内核对象结构中;然后装载另一个线程的上下文,将CPU执行控制交给此线程,如果该线程有另一个进程拥有,那么在装载该线程之前,Windows还必须使得CPU能够处理该虚拟地址空间。Windows操作系统为各个线程每次分配大概30毫秒的执行时间,称为“时间片”。上下文切换是净开销,不会换来任何在存储空间或者性能上的收益。但是能向用户提供一个健壮的能灵活相应的操作系统。

空间开销测试

测试代码:


代码如下:

static void Main(string[] args)
         {
             List<Thread> threadList = new List<Thread>();
             for (int i = 0; i < 1000; i++)
             {
                 Thread thread = new Thread(
                     new ParameterizedThreadStart(o => { Console.WriteLine("第{0}线程", o); Thread.Sleep(100000); })
                     );
                 threadList.Add(thread);
             }
             Console.ReadLine();
             for (int i = 0; i < threadList.Count; i++)
             {
                 threadList[i].Start(i);
             }
             Console.ReadLine();
         }

pslist的观测结果结果:

程序开始运行时

所有线程Start以后

1000个线程Start以后虚拟物理内存占用量增加了31MB,而虚拟内存增加了1000MB之多。

线程池

  由于专用线程(实例化Thread类所创建的线程)存在的巨大内存和性能开销,CRL被设计出支持线程池技术,为应用程序提供线程管理。每个CLR独立维护一个自己的线程池,CLR在线程池中只建立必要的线程供给应用程序使用,在应用程序把多个任务分配给线程池后,CLR将任务轮流分配给线程池中线程来执行,当任务执行完毕后,线程池中的线程并不会回收,而是等待分配新的任务。这就能有效的减少线程的数量,并且减少了线程创建时的性能开销(线程池相关的内容笔者正在整理之中,后续会陆续发布)。另外,对于Thread类有一个实例属性IsBackground指示线程是前台还是后台运行,前台线程指线程所在进程关闭时,进程需要等待线程执行完成才能关闭,后台进程指当进程关闭时,线程立即停止执行,不会等待执行完成既退出运行。该属性默认值为True,即专用线程默认为前台线程。

进程、AppDomain和线程

  这里涉及到进程,AppDomain和线程,我觉得有必要把这三个概念放一起做一个大体的比对。

  1、进程是操作系统为应用程序虚拟的执行地址空间,应用程序中的所有数据都装载在相互独立的进程中运行。

  2、AppDomain是.NET托管应用装载的内部相互隔离的托管执行空间。如:IIS进程中所有的Web应用都运行在独立的AppDomain中。

  3、线程是应用程序内部虚拟化的CPU执行单元,操作系统对内存中所有应用程序进程中的线程进行调度,交给CPU进行执行。

  进程是一个虚拟的地址空间,操作系统不会对其进行调度而是调度执行其中包含的线程。CLR在AppDomain内部也有自己的运行线程,AppDomain中的线程由CLR维护,但最终CLR仍需将AppDomain中的线程映射为应用程序进程中的线程,交给操作系统进行调度。并且AppDomain中的线程不一定与操作系统线程完全一一对应。

  下图是大体描述进程、AppDomain和线程的关系,以及操作系统对线程的调度。详细内容本文不做进一步说明。

线程优先级

  线程调度根据线程优先级进行,Windows将系统内的线程分为0至31,共32个等级,优先级为31的线程是最高优先级线程,最先得到执行权限。例如,一个优先级为8的线程正在执行,而此刻操作系统确认一个优先级为31的线程已经做好了执行准备,那么操作系统会立刻挂起正在执行的线程,把CPU的执行权限交给优先级为31的线程,即使优先级为8的线程还没有执行完一个完整的时间片,优先级为31的线程将获得一个完整的时间片,如果该线程执行完后操作系统发现还有优先级为31的线程准备执行,那么CPU执行权限将分给这个线程,前面优先级为8的线程始终得不到执行,这种情况称为饥饿。

  ​​如果开发人员没有合理的设置自己程序内的线程优先级,就可能会造成其他应用程序很难得到执行,甚至影响计算机响应速度。所以Windows又设计了一个进程优先级类来控制各个进程中的线程优先级的关系,进程优先级是一个虚拟的概念,因为操作系统不会对进程进行调度,这个概念只是为了控制进程中线程优先级的范围。进程优先级有6个级别(详见下表),根据进程优先级类,应用程序对内部线程设置相对优先级,会得到一个操作系统调度的线程优先级值。这样,使线程优先级能得到有效控制。






































































线程相对

优先级

进程优先级类

Idle

Below Normal

Normal

Above Normal

High

Real-Time

Time-critical

15

15

15

15

15

31

Highest

6

8

10

12

15

26

Above normal

5

7

9

11

14

25

Normal

4

6

8

10

13

24

Below normal

3

5

7

9

12

23

Lowest

2

4

6

8

11

22

Idle

1

1

1

1

1

16

(0)

相关推荐

  • .NET 线程基础的使用介绍

    线程作用及开销 早期计算机一次只能运行一个程序,长时间执行程序容易出现计算机"瘫痪"的问题,如果程序进入死循环则只能重启系统.即使计算机不崩溃,也难免让用户崩溃.为了解决这个问题,操作系统设计者设计出了进程的概念,使得每个应用程序运行在一个虚拟的内存空间中.进程中又包含多个线程,CPU则根据操作系统调度执行每个进程中的线程任务.通过线程这种对CPU的虚拟化管理方式,操作系统形成了多任务执行的机制.但与一切虚拟化机制一样,线程会产生空间和时间的开销.这其中的开销包括: 1.线程内核对象.

  • java并发编程专题(一)----线程基础知识

    在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线程状态图: 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权. 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码. 4

  • java基础之NIO介绍及使用

    一.NIO java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络. 二.三大组件 NIO三大组件:Channel.Buffer.Selector 1.Channel 和Buffer Channel是一个对象,可以通过它读取和写入数据.拿 NIO 与原来的 I/O 做个比较,通道就像是流,而且他们面向缓冲区(Buffer)的

  • C#多线程开发实战记录之线程基础

    目录 前言 线程基础 1.创建线程 2.暂停线程 3.线程等待 4.线程终止 C#中的lock关键字 总结 前言 最近由于工作的需要,一直在使用C#的多线程进行开发,其中也遇到了很多问题,但也都解决了.后来发觉自己对于线程的知识和运用不是很熟悉,所以将利用几篇文章来系统性的学习汇总下C#中的多线程开发. 线程基础 "进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元" 这句话应该学习计算机的朋友或多或少都听说过,这在操作系统这门课中是很重要的一个概念. 在操作系统中可以同时

  • c#多线程之线程基础

    目录 一.简介 二.创建线程 三.暂停线程 四.线程等待 五.终止线程 六.检测线程状态 七.线程优先级 八.前台线程和后台线程 九.向线程传递参数 十.使用C# Lock 关键字 十一.使用Monitor类锁定资源 十二.处理异常 一.简介 1.为了防止一个应用程序控制CPU而导致其他应用程序和操作系统本身永远被挂起这一可能情况,操作系统不得不使用某种方式将物理计算分割为一些虚拟的进程,并给予每个执行程序一定量的计算能力.此外操作系统必须始终能够优先访问CPU,并能调整不同程序访问CPU的优先

  • Linux shell脚本基础学习详细介绍(完整版)第1/2页

    Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提. 1. Linux 脚本编写基础 ◆1.1 语法基本介绍1.1.1 开头程序必须以下面的行开始(必须方在文件的第一行):#!/bin/sh符号#!用来告诉系统它后面的参数是用来执行该文件的程序.在这个例子中我们使用/bin/sh来执行程序.当编辑好脚本时,如果要执行该脚本,还必须使其可执行.要使脚本可执行:编译

  • python开发之thread线程基础实例入门

    本文实例讲述了python开发之thread线程基础.分享给大家供大家参考,具体如下: 说到线程,我们要知道啥是串行,啥是并行程序 举个例子: 串行程序,就是一个一个的执行程序 #python threading import time ''' 每一秒中,输出:this is a demo! ''' def serial(): '''串行输出''' time.sleep(1) print('this is a demo!') def main(): for i in range(5): seri

  • Kotlin 基础语法详细介绍

    Kotlin 基础语法详细介绍 基础语法 定义包名 包名的定义应当在源文件的头部 package my.demo import java.util.* // ... 文件路径和包名并不要求匹配,源文件可以被放置在文件系统任意位置 参考:包 定义函数 函数有两个Int类型参数和Int类型返回值: fun sum(a: Int, b: Int): Int { return a + b } 函数体中只有一个表达式并且作为函数的返回值: fun sum(a: Int, b: Int) = a + b 函

  • c# 面试必备线程基础知识点

    线程的知识太多,知识点有深有浅,往深的研究会涉及操作系统.CPU.内存,往浅了说就是一些语法.没有一定的知识积累,很难把线程的知识写得全面,当然我也没有这个能力.所以想到一个点写一个点,尽量总结一些有用的知识点.线程是个大话题,这个系列可能会有好几遍关于线程的,先从基础的开始,热热身. 一些基础概念 线程(Thread)是操作系统能够进行运算调度的最小单位.它是进程中的实际运作单位,一个进程中可以启动多个线程,每个线程可以并行执行不同的任务.严格意义上来说,同一时间可以并行运行的线程数取决于 C

  • 一文带你了解Python 四种常见基础爬虫方法介绍

    一.Urllib方法 Urllib是python内置的HTTP请求库 import urllib.request #1.定位抓取的url url='http://www.baidu.com/' #2.向目标url发送请求 response=urllib.request.urlopen(url) #3.读取数据 data=response.read() # print(data) #打印出来的数据有ASCII码 print(data.decode('utf-8')) #decode将相应编码格式的

随机推荐