hey,you guys.
好久不见了,最近忙着学习英文,处理一些杂事,所以没有来得及更新博客。公司目前没活,比较清闲。所以,有时间研究了一下<叩响C#之门>。据说作者是一位40多岁的初中数学老师,自学C#。40多岁的人自学编程,这份毅力很令人敬佩。这本书写的,是C#语言的基础知识。作者讲解的很清楚,读后很受益。很多之前一知半解,甚至一点都不懂得基础原理,现在豁然开朗。就像孔子所说:“朝闻道,夕死可矣。”这本书,真的适合初级.NET程序员读一下,真的很有帮助的。好了,书归正传,来开始我们今天的学习吧。
多线程这块,在.NET中属于深层次的技术了。今天,我们就来学习学习多线程的知识吧。
线程的概念
大家平时使用电脑,可以一边听音乐,一边下载电影,一边浏览网页。那么电脑它是如何满足用户这种需求呢?原来操作系会创建三个应用程序,通过应用程序来执行用户的操作。每个应用程序被看作一条连续的指令流,CPU一条条执行这些指令流。但是早一些的电脑CPU不允许同一时间执行多条指令流,那么电脑又是如何做的呢?操作系统创建三个应用程序的同时,也创建了三个进程(Process)。进程不但包括了应用程序的指令流,还包括了运行应用程序所需的内存、寄存器等等。操作系统通过进程来执行应用程序。我们可以把进程看作是执行路线。操作系统 通过”时间片轮转”来轮流执行这些进程。所以,宏观上进程是并发执行的,在微观上是交替进行的。也就是说,电脑并不是同时满足听音乐、下载电影、浏览网页的,CPU是交替执行的,只不过交替时间很短暂,我们感觉不到。我们设想这样一个场景,一个网页播放Flash的同时,网页上还有一个文本框,来接受用户的输入。这个网页需要一边播放Flash,一边接受用户的输入。Note (个人理解,CPU执行这个网页的进程指令,同时这个进程还要执行播放Flash、接受用户输入这两个指令)。如果是以前,我们需要很复杂的一段代码来实现这个需求。但是多线程(Multi-Threading)技术的出现,就可以很容易的实现这个需求了。可以在网页这个进程中创建两个线程,通过这两个线程来执行播放Flash、接受用户输入。我们可以把进程中的多个线程,看作是多条执行路线。一个进程中的多个线程,它们共享资源。所以,线程之间的切换要远远快于进程之间的切换,我们可以把线程看作是轻量级的进程(LightweightProcess)。操作系统通过调度程序来管理线程,不需程序员关心,我们只需编写好线程即可,线程是CPU调度的基本单位。
Thread类
计算机运行某个应用程序时,会创建一个进程,进程会创建多个线程,通过线程来执行工作,我们把执行某些工作的线程称为工作线程(Work Thread)。C#中,关于线程的处理,都是通过System.Threading命名空间下的Thread类来完成的。
Using System.Threading; //别忘了添加引用 //声明一个线程的代码 Thread workThread=new Thread(entryPoint);
entryPoint:是入口方法,线程会从入口方法的第一行代码执行。大家应该可以看出,Thread类的构造函数的参数类型是一个委托类型。也就是说entryPoint的函数的返回值、参数取决于Thread类的构造函数的委托参数决定。
//Thread类的构造函数参数的委托类型 public delegate void ThreadStart(); public delegate void ParameterizedThreadStart(object obj);
入口方法,必须是ThreadStart或ParameterizedThreadStart的委托。
那么综上所述,如果要创建一个线程,需要两步.
//第一步 创建入口方法 private void EntryPoint() { //线程的具体代码 ........... ........... } //第二步 创建线程对象 Thread workThread=new Thread(EntryPoint);
线程的优先级
我们工作生活中,需要处理很多事情。一般当紧的事儿需要及时处理,不当紧的事儿可放在后面处理。线程处理指令也是一样的,也分当紧与不当紧。通过Thread类的Priority属性来设置线程的优先级。Priority属性是一个ThreadPriority枚举。这个枚举有以下5个优先等级:
Normal、AboveNormal、Highest、BelowNormal、Lowest。
我们跑一下程序来测试一下线程优先级:
//workThread1线程 Thread workThread1 = new Thread(delegate() { for (int i = 0; i < 100000000; i++) { if (i % 1000000 == 0) { Console.Write("A"); } } }); //workThread2线程 Thread workThread2 = new Thread(delegate() { for (int i = 0; i < 100000000; i++) { if (i % 1000000 == 0) { Console.Write("B"); } } }); //启动线程 workThread1.Start(); workThread2.Start();
运行程序,效果如下图:
QQ Photo20140822112205" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="QQ Photo20140822112205" src="/Upload/Images/2014082216/11B056B20484FC7E.jpg" width="644" height="414" />
我们通过效果图看以看出,两个线程基本是平均交替执行的,看不出优先级的高低。因为,默认的线程优先级是一般(Normal)级别的。下面,我们修改一下程序,改变线程的优先级:
//workThread1线程 Thread workThread1 = new Thread(delegate() { for (int i = 0; i <= 500000000; i++) { if (i % 1000000 == 0) { Console.Write('A'); } } }); //workThread2线程 Thread workThread2 = new Thread(delegate() { for (int i = 0; i <= 500000000; i++) { if (i % 1000000 == 0) { Console.Write('B'); } } }); //修改Thread的优先级 workThread1.Priority = ThreadPriority.AboveNormal; workThread2.Priority = ThreadPriority.BelowNormal; //启动线程 workThread1.Start(); workThread2.Start(); //主线程代码 for (int i = 0; i <= 500000000; i++) { if (i % 1000000 == 0) { Console.Write('M'); } }
其实除了workThread1、workThread2还有一个主线程(Main Thread)。我们修改了一下主线程的代码,以便观察三个线程是如何交叉工作的。我们代码中把workThread的Priority的属性设置为高于一般(AboveNormal)、把workThread2的Priority属性设置为低于一般(BelowNormal)。此时运行程序的效果如下图:
Note that:优先级高,只能证明占用CPU的时间长,并不代表,只有执行完优先级高的线程,才会执行优先级低的线程。在图中,我们也可以看出,B也回出现在A之前的。
线程插入
我们可以通过Thread类的Join()方法,将两个原本交替执行的线程,变为顺序执行。我们通过代码来观察这个Join()方法:
static void Main(string[] args) { //workThread1线程 Thread workThread1 = new Thread(delegate() { for (int i = 0; i <= 500000000; i++) { if (i % 1000000 == 0) { Console.Write('A'); } } }); //workThread2线程 Thread workThread2 = new Thread(delegate() { for (int i = 0; i <= 50000000; i++) { if (i % 1000000 == 0) { Console.Write('B'); } } workThread1.Join(); for (int i = 0; i <= 50000000; i++) { if (i % 10000 == 0) { Console.Write('b'); } } }); //启动线程 workThread1.Start(); workThread2.Start(); }我们在线程workThread2的入口方法中,调用了workThread1的join()方法,此时当程序执行到join()方法时,workThread2就会停止工作,去执行workThread1,直到workThread1执行完毕,才会接着执行workThread2。程序运行结果如下图:
Join()方法,还可以接受一个表示毫秒的参数,当达到这个时间,不论是否执行完毕,都会退出。
线程状态
线程一共有7种状态,它们分别是未开始状态(UnStarted)、运行状态(Running)、等待睡眠插入状态(WaitSleepJoin)、挂起请求状态(SuspendRequested)、挂起状态(Suspended)、中止请求状态(AbortRequested)、中止状态(Stopped)。大家可以通过下面这幅图片来认识这7中状态:
1.未开始状态(Unstarted)
当一个线程被创建,它的状态会变为未开始状态(UnStarted).
2.运行状态(Running)
当线程调用Start()方法时,线程状态就会变为运行状态(Ruuning)。
3.等待睡眠插入状态(WaitSleepJoin)
当运行状态的线程调用方法Sleep()、Join()、或Wait()时,线程状态会变为等待睡眠插入状态(WaitSleepJoin).此时如果调用Pulse()或Interrupt()线程状态会变为Running状态。
4.挂起请求状体(SuspendRequested)
当运行状态的线程调用方法Suspend()的时,线程状态会变为SuspendRequested。
5.挂起状态(Suspended)
当调用Suspend()方法时,线程不会立马被挂起,而是会处于SuspendRequested状态,线程会再执行几条指令,当确保线程在一个安全的状态下,挂起线程,线程状态变为Suspended。调用Resume()方法可以使线程状体变为Running状态。
6.中止请求状态(AbortRequested)
当处于运行状态的线程调用方法Abort()时,线程状态会变为AbortRequested。
7.中止状态(Aborted)
当线程调用Abort()方法时,线程不会马上中止,而会处于AbortRequested状态,再执行几条指令,当确保线程在一个安全状态下,中止线程,线程状态变为Stopped。