首页 > 代码库 > 漫谈多线程(上)

漫谈多线程(上)

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

我们通过效果图看以看出,两个线程基本是平均交替执行的,看不出优先级的高低。因为,默认的线程优先级是一般(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)。此时运行程序的效果如下图:

QQ Photo20140822135626

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。程序运行结果如下图:

QQ Photo20140822141249

Join()方法,还可以接受一个表示毫秒的参数,当达到这个时间,不论是否执行完毕,都会退出。

线程状态

线程一共有7种状态,它们分别是未开始状态(UnStarted)、运行状态(Running)、等待睡眠插入状态(WaitSleepJoin)、挂起请求状态(SuspendRequested)、挂起状态(Suspended)、中止请求状态(AbortRequested)、中止状态(Stopped)。大家可以通过下面这幅图片来认识这7中状态:

QQ Photo20140822142019

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。

漫谈多线程(上)