线程介绍
2019-11-08

一、基本概念介绍

 

线程是windows系列操作系统中的名称。对应linux系列中的说法叫“任务”。是具体的:最小CPU执行单元

从定义上可以线程直接和硬件扯上关系,那么先介绍下硬件CPU。下面是本机CPU信息图:

 

 

上图中有很多CPU硬件参数信息,本介绍中只重点关注下面红色的核心和线程两个参数。

 

Cores:CPU物理核心数。 Threads:CPU可同时执行的线程数。具体解释就是该CPU有两个物理核心。可以同时工作,每个核心分别执行一个线程,同时执行两个线程。

另外还有一种说法,比如酷睿系列的双核四线程,四核八线程等的解释是:四个物理核心,“同时”执行8个线程。这里的同时是有引号的。因为:

 

并发的严格意义上讲:一个核心是没有办法同时处理两件任务(线程)的。只是:从CPU的工作频率上讲是很快的。同时完成两个任务的时间和一件任务基本没有什么差别(即使有差别,也是可以忽略的。因为这个差别即使以CPU的时间精度来度量也很难感知)。所以让人误以为是两个线程是在同时执行。

 

(参考运动员比赛的计时方式可以大概了解,人的时间精度感知最多就是0.1秒了。而CPU可以到纳秒级别,所以在一个核心上“同时”运行两个任务的技术称为“超线程”技术,跟一个核心运行一个任务相比的时间差异,CPU也感觉不出来。)

 

 

二、.NET线程用法介绍。

 

从.NET2.0的Thread开始,到.NET4.0运行的Task,以及Parallel的并行计算支持。都是对线程的使用。

 

Thread:操作系统级别的线程。线程的状态以及CPU调度等直接由操作系统直接干预。所以当线程内发生严重异常时可能会被操作系统直接关闭,而不通知程序代码。

ThreadPool:依然是操作系统级别创建的线程,和Thread独立创建的线程相比,它创建的线程受“线程池”管理。缺点是受池管理的线程不支持中断、取消等操作。优点:受池管理,减少CPU任务调度的开销,提高CPU利用率。

 

Task:4.0开始由.NET CLR控制的一个高级版“线程池”,当然仅限于Task.Factory.StartNew方法创建的线程,如果是new Task()方法的话,那创建的线程和Thread一样,也是操作系统级别的线程。

 

上面讲到线程的CPU调度,其实线程并不是创建越多越好。因为:当CPU核心切换线程时,是有一定成本的。要从CPU寄存器把当前时间片用完的线程的数据转移到内存。然后再从内存装载要切换线程的数据和指令集到寄存器中。这个成本对CPU而言不小。所以线程切换越频繁,CPU工作效率越低。这也是我们编程中要注意的一个设计原则:单线程处理IO,多线程处理计算的原因之一。(当然多线程处理计算这个线程数也要有一个度,超过这个度,就会适得其反。)

 

三、.NET线程应用举例。

 

1,  web程序IIS中每个应用程序池,会创建一个w3wp.exe进程。建议每个站点独立一个应用程序池,避免多个站点共用一个池。那么线程就对应站点的每个请求了。IIS会为每个客户端请求在w3wp.exe进程中创建一个(线程池)线程,走完asp.net生命周期的每个过程,处理完请求,然后响应给客户端结果。完了再由IIS回收该线程到线程池。

2,  WINFORM程序中默认开始一个UI主线程,并在此线程上创建各个窗体,各个控件。其中BackgroundWork控件就封装了线程的创建,运行和回收。开发人员也会根据根据需求自行开启线程。

3,  WCF通讯中,也会根据实例配置模式来决定开启一个或多个线程来处理请求。

4,  Socket通信中通常也会结合线程(池)来处理实时通信。

 

四、线程与并发的概念

 

两个线程同时操作某一资源时就会产生并发,并发往往就会产生冲突的问题。那么解决冲突通常的两个方法:队列和加锁。队列就是产生线程时按某种规则将线程对象排序,每次执行一个线程对象。加锁就是线程的产生不加控制,对线程访问操作的同一资源加以控制。无论哪个线程,每次只允许一个线程对象操作该资源。这两种方式都能解决冲突,保证资源的一致性。但是会牺牲一定的性能。因为多线程的设计就是从提升性能出发的。C#常见的加锁方式:lock关键字,Mutex,Monitor等类。

 

 

五、关于线程的CPU调度

 

每个进程都至少包括1个线程。如下图:

 

 

Devenv.exe进程中总共有31个线程。并不是全部都在运行。大部分在休眠中。当某个就绪的线程被调度器激活时就会将该线程的数据和指令从内存加载到CPU寄存器。并拥有一段工作时间。

 

调度器是操作系统层面的一个管理容器,负责如何分配CPU的工作。不同操作系统根据硬件构架适用不同的调度算法。常见的有时间片轮转法、多级队列排队调度,抢占式/非抢占式调度法等。常见的x86构架的操作系统,基本上都应用于时间片轮转法。

 

时间片:是属于CPU执行时间的一个单位。具体是多少尚无确定,跟OS设计相关可能是几十纳秒。

 

操作系统调度程序会根据各个线程的状态,优先级等各种综合条件随时调度某个线程参与执行。被调度的线程必需是就绪状态的:比如,等待IO的或者被阻止的线程不会被调度执行。被调度的线程拥有一定时间片的CPU使用权。在使用时间用完时,线程没有执行完,如果有其它更高级别的线程就绪需要被执行时,也会被调度器卸载下来再装载其它线程。

不同的操作系统对时间片轮转法的调度器执行细节可能也不一样,比如winxp和win7就存在细微差别。在xp下,某个进程中存在死循环导致CPU被100%使用,则会直接造成系统假死,甚至不能响应键盘,鼠标等操作。而在win7下,仅会造成该进程假死,操作系统保留有一定CPU时间来处理响应键盘,鼠标等底层的硬件响应操作。所以win7系统更具有健壮性和稳定性。

 

六、关于线程的使用注意事项

上面说到的这些线程知识点,那么使用线程时需要注意的主要以以下几点:

1,避免并发冲突。线程中不要随意操作静态变量、IO、连接和端口等资源性对象。

2,不要随意过多的创建线程,掌握其度。线程较多尽量使用线程池管理。

3,跨线程操作注意线程安全,如winform的跨线程操作控件。

4,大批量IO操作尽量保证单线程处理。

5,  注意线程的异常处理,线程中使用的资源注意回收。

 

七、总结

 

线程一门既有深度,牵涉面也较广的技术。上面谈到的几点比较杂乱,不过涉及到的技术面基本上都讲到了。后面再整理成系统性知识分享给大家。

 

 

 

 

 

, 1, 0, 9);