C# 多线程学习系列一

一、Windows线程的由来  关于操作系统的一些知识

(1)、单个"工作线程"的问题  

早期的Windows没有线程的概念,整个系统只有一个"工作线程",上面同时跑着操作系统代码和应用程序代码.这种方式最大的缺点就是,一个应用程序运行时会霸占整台机器(应为只有一个工作线程),且当它发生死循环时,会造成PC停止工作.如果此时重启,更shit的是,所有的应用程序都会停止,且丢失数据.

 

(2)、Windows进程

i、什么是Windows进程,以及它解决的问题

MS为了解决单个"工作线程"的问题,设计了新的内核,该内核实现了Windows进程的功能,每个Windows进程(应用程序要使用的资源集合)运行一个应用程序,如下图:

一个Chrome浏览器进程包含了很多子进程(子进程可以共享父进程的资源),后面包含了正在使用的资源集合,包括CPU、内存等.每个进程都有一个虚拟空间地址(PID).

当一个应用程序应为代码故障发生卡死等问题,并不会影响其他的应用程序的运行,只需要打开任务管理器,将该进程关闭即可.其他应用程序的数据也不会丢失,因为它们是彼此独立的进程.

 

ii、Window进程的安全性

在Windows中,进程之间不能相互访问(不包括父子进程),单个进程也无法访问Windows内核.

 

iii、关于CPU的问题

虽然Windows进程很好的解决了单个"工作线程"的问题,Windows不会发生一个应用程序崩溃,所有应用程序全都停止且所有运行着的应用程序的数据丢失的情况。但是如果PC只有一个CPU,当CPU本身发生死循环等问题,还是会导致PC停止工作.

 

iv、什么是Windows线程,以及Windows线程解决的问题

MS为了解决单个进程执行异常,导致CPU停止工作的问题,设计了Windows线程,它的作用是对CPU进行虚拟化,Windows会给每个Windows进程分配一个Windows线程,该线程相当于一个虚拟的CPU(包含CPU所有的功能),如果应用程序的代码进入死循环,相关进程会被停止,但是其他的应用程序进程并不会停止,会继续执行.因为它们拥有自己的线程(虚拟CPU).

 

2、Windows线程的消耗

虽然Windows线程保证了Windows的可靠性和健壮性,但是天下没有免费的午餐,随之带来的肯定是其他的PC资源消耗.这里不想介绍太多操作系统级别的东西,只说一些直观的我们能看到的.就以我的笔记本为例,打开任务管理器如下:

我的笔记本此时跑着176个进程,所以理论上至少有176个线程,但是实际却有2103个线程,平均每个进程12个线程.下面是我笔记本的配置

双核,理论上最优的配置是,只有两个线程,应为涉及到线程上下文切换(从一个线程上下文切换到另一个上下文),而上下文的切换的性能代价是十分大的.

我的CPU利用率为7%,说明93%的时间,这2103个线程啥事都没干,严重的浪费了我的内存.如果这个时候开启了远程桌面服务,假设10个用户连了我的笔记本,所有的开销会翻倍.

当然虽然线程的开销很大,但是相比于创建进程,开销相比较小.但是开发应用程序的时候,还是要合理的使用线程!

 

二、为什么要使用多线程

主要有两点,拿Windows来举例

(1)、Windows使用线程,每个线程拥有一个虚拟的CPU,包含CPU所有的功能,当一个应用程序使用的线程因为代码问题,发生故障时,Windows会关闭这个线程,但是这个线程使用的是虚拟的CPU,所以不会影响其他应用程序使用它们的线程,也就是虚拟CPU.所以线程保证了CPU的高可用.同时现在的电脑往往都有多个CPU,利用这个特性,我们可以使用代码创建多个线程,去执行不同的任务,比如一个Windows桌面程序,可以让主线程去处理用的输入,创建其他的线程去处理桌面程序的UI界面.这样用户体验就会非常好.不会说必须等到桌面UI全部初始化完,用户才能执行输入操作.

(2)、Windows每个CPU就能调度一个线程,如果你的PC有个多CPU,那么它们就能协同操作,同时也就是并发的执行多个任务.但是虽然线程能并发执行很多任务,但是如果创建的线程过多,理论上一个CPU只能调度一个线程,但是你创建的线程如果比CPU还多,那么windows就会进行上下文切换,这个性能损失是很可观的(不考虑创建线程本身的开销),所以关于线程创建的数量,还是需要慎重考虑.

 

三、关于CPU利用率

CPU利用率简单点说,就是CPU的使用效率,如果当PC长时间的响应I/O,或者在驱动硬件设备干活,而CPU本身却很空闲,这种情况CPU利用率就很低.

 

四、线程调度和优先级

1、线程调度

首先Windows是一个抢占式的操作系统,抢占式操作系统说白点就是线程的切换,因为大多数Windows操作系统运行了不止一个应用程序,而这些应用程序运行了不止一个线程,最后这些所有的线程加起来肯定超过PC的CPU核数(上面说了一个CPU只能调度一个线程),那么为了保证这些线程能尽可能的同时运行,所以Windows操作系统必须进行频繁的上下文切换,保证这些线程能同时调度.这也就是为什么你在Windows的操作系统同时开启多个应用程序会非常卡的原因.因为一下次有N多个线程同时开启,导致CPU工作压力飙升.

那么为什么说是抢占式的呢?因为每个内核包含一个上下文对象,这个对象包含了线程上次执行完毕的CPU寄存器状态,在一个CPU的时间片执行完毕之后(也就是一个线程执行完毕之后),Windows会便利所有的上下文,找出一个适合调度的上下文,并切换到这个上下文,执行该线程.这就是所谓的抢占式.Windows可以在任何时间抢占当前正在执行上下文,切换到下一个上下文,只要Windows觉得当前上下文更值得被调度。注:这是个很复杂的过程.所以你无法确保创建的线程会在什么时候被调度,以及调度多长时间.这些都由Windows来判断.说白点,它比你更聪明.并且他考虑的比你更全面.哈哈!

 

2、优先级

虽然你无法确定你的线程什么时候被调度,但是当你有一个线程这个线程执行非常重要的任务,但是Window内核本身并不能保证它的执行优先级,所以我们必须使用其他的手段来确保它能优先于其他线程的执行.万幸的是,Window也考虑到了这一点,设计了优先级这一概念,来满足我们的业务需求.

(1)、进程优先级

Windows支持6个进程优先级类,如下:

Idle(空闲),一般执行在操作系统空闲的时候执行的程序,比如Windows自动更新服务.屏幕保护程序等.

Below Normal(低于普通优先级)

Normal(普通优先级)

Above Normal(高于普通)

Hign(高)如果又非常关键的进程,可以使用.

Realtime(实时,超高优先级)不建议使用,会影响操作系统的进程,会造成"死机"的情况,使用这个优先级有权限要求(管理员有这个权限)

 

(2)、线程优先级

Windows为线程定义了0~31的优先级,Windows会优先调度高优先级的线程,注意,会存在线程抢占的情况,如果开启了一个应用程序,当该程序里面有一个31优先级的线程,那么Windows会挂起当前正在执行的低优先级的线程,给该线程使用.单核机器,如果存在过多的较高优先级的线程,那么低线程将不会被调度,这是种很shit的情况,所以要谨慎使用.

Windows提供了7个线程优先级

Idle(空闲)操作系统空闲时执行的线程

Lowest(最低级别)

Below Normal(低于正常)

Normal

Above Normal

Hignest

Time-Critical(实时)

 

(3)、关于进程优先级和线程优先级的联系

真实的线程优先级的大小取决于进程优先级,如果两个进程优先级一样,那么只看线程优先级大小.具体请看CLR via C#提供的这张表

 

  

 

posted @ 2018-11-05 15:19  郑小超  阅读(753)  评论(1编辑  收藏  举报