多线程在web开发里面其实应用场景并很少,并且应用到多线程的场景也大多都是一些比较简单的场景,基本上大多均可以用Task代替,因此不少web开发人员对多线程的理解很是的浅薄,也就致使了会出现不少不可预计的bug,而后又所以写了一大堆逻辑来绕来绕去,因此我想谈谈多线程,试图作到高屋建瓴,给你们一个比较开阔的视野。web
这一篇先从性能角度来讲,下一篇从线程安全角度来说解。算法
先解释一下,为何线程不是越多越好:数据库
谈到多线程必需要谈到cpu,一个单核心cpu在同一个时间内,只能执行一个线程(受限于目前技术),对于超线程cpu(intel专利,使用了超线程技术的cpu,允许一个核同时执行两个线程,在windows操做系统里面也会显示有两个核心),咱们都理解成是双核心cpu。windows
而对于每一个线程,都必定包含如下几个要素:缓存
一、线程内核对象,系统为每一个线程建立的,包括上下文(context)等。安全
二、线程环境块,是一块内存,包含线程的异常处理链首。多线程
三、用户模式栈、内核模式栈。性能
四、DLL线程连接和线程分离通知spa
这个是咱们在建立一个新线程时,系统在为线程初始化时须要建立的东西。操作系统
而前面又提到了,一个单核CPU同一时间内只能跑一个线程,那么当一个CPU正在跑一个线程的时候,这个线程的不少数据已经存到了CPU的高速缓存中,这个时候发生了切换,咱们须要切换到另一个线程上面去执行,那这个线程执行的多是另外的代码,须要读取另外的数据,这个时候CPU就须要从新去内存里面去取数据,来填充这个高速缓存。而CPU去内存里面取数据的这个过程,相比于去高速缓存里面取数据来讲,是很慢的,这也就是说,当咱们频繁切换线程的话,CPU就须要作不少额外的事情。这也就是说,当只有单核CPU时,一样的功能,一个线程确定比多个线程更快。这里有一个矛盾,就是系统不可能只有一个线程,系统还牵涉到不少本身的系统线程,以及其它的应用程序的线程,那系统在作线程切换(系统决定调用哪一个线程)的时候,会牵涉到一个线程优先级的问题,而这个调度方法(相对合理智能的一个算法)是咱们不可控的,因此说当你的程序有多个线程的状况下,在作线程切换的时候,切换到你的线程上面的几率会变大,因此也不能绝对的说一个线程就必定比多个线程快,这里只是一个大概的相对理论状况。
既然是这种状况,那咱们在写代码的时候,就应该尽量的避免出现线程切换,而让CPU尽量的执行该线程。但是在什么状况下会容易出现线程切换(如下全部的线程切换都是指的相对或是可能,由于线程调度不可控)。
好比我有如下代码:
public static string ReadText(string path) { string text = ""; if (File.Exists(path)) { using (Stream fs = File.Open(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); sr.Close(); } } } return text; }
这个是很常见的IO读取,这个时候会给IO线程发出一个请求,而后等待IO的响应,在等待的过程当中,系统会把这个线程锁定(这是个很棒的设计),让CPU去作其它事情,等到执行响应完毕之后,再唤醒该线程。同理,在作数据库的读取以及一些其余的IO请求时,都会这样。假如当有一个用户请求时,咱们就会执行这样一段代码,那当有不断多的用户请求时,系统就会建立不断多的线程(建立线程的自己开销就很昂贵),而当IO读取完响应的时候,又会有不断多的线程逐渐被唤醒,系统这个时候就又会疲于线程切换,你就会发现性能开始巨降。
同理,当我有如下代码时:
lock(object){ ... }
当多个线程在执行这段代码的时候,就会出现颇有意思的状况。
假如1-10,一共有10个线程须要执行到这段代码,假如cpu是2个(分别为A、B,分别执行的是一、2),当1执行的时候,2被锁定,这个时候B CPU就开始作线程切换,调度3-10中的任意一个继续执行,若是这段代码较长,A不必定能在短期内执行完,那就会出现,调度一个锁一个,继续切换,再调度,再锁定,再切换的一个循环,一直到全部线程都被锁定为止。
当1执行完毕的时候,这个时候唤醒全部被锁定的线程,而后从新分配给A、B两个CPU,而后当A又执行到这里的时候,B又会出现刚刚再调度、再锁定,再切换这样的一个循环状况。因此在多线程开发的时候,尽量的避免共享资源的出现。
ps:那当咱们在作多线程开发的时候,何时用线程池、何时本身写线程。Task也能够理解为线程池。
线程池的优点就是,当逻辑执行完毕之后,并不销毁线程,而是将线程挂起,当你须要使用线程的时候,就会给你分配一个空闲的线程去执行你的逻辑,让线程反复使用,由于前面有提到了初始化线程须要建立不少对象,开销很昂贵。只有当全部的线程都处于繁忙状态时,没有线程分配时才去给你从新建立一个新线程。
但是若是你的程序在某一个时间段有一个峰值的话,那么最繁忙的时候,程序就会建立N多个线程,而当峰值过去了之后,这些被建立的线程不会被释放掉,会一直占用这你的资源。一直等到GC,才有可能被释放掉。
因此各位看客根据本身的业务状况来决定,是否使用线程池。