C多线程机制Word下载.docx

上传人:b****8 文档编号:22227298 上传时间:2023-02-03 格式:DOCX 页数:16 大小:45.24KB
下载 相关 举报
C多线程机制Word下载.docx_第1页
第1页 / 共16页
C多线程机制Word下载.docx_第2页
第2页 / 共16页
C多线程机制Word下载.docx_第3页
第3页 / 共16页
C多线程机制Word下载.docx_第4页
第4页 / 共16页
C多线程机制Word下载.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

C多线程机制Word下载.docx

《C多线程机制Word下载.docx》由会员分享,可在线阅读,更多相关《C多线程机制Word下载.docx(16页珍藏版)》请在冰豆网上搜索。

C多线程机制Word下载.docx

一个公司至少得有一个职员吧,同理,一个进程至少包含一个线程。

在公司里,你可以一个职员干所有的事,但是效率很显然是高不起来的,一个人的公司也不可能做大;

一个程序中也可以只用一个线程去做事,事实上,一些过时的语言如fortune,basic都是如此,但是象一个人的公司一样,效率很低,如果做大程序,效率更低——事实上现在几乎没有单线程的商业软件。

公司的职员越多,老板就得发越多的薪水给他们,还得耗费大量精力去管理他们,协调他们之间的矛盾和利益;

程序也是如此,线程越多耗费的资源也越多,需要CPU时间去跟踪线程,还得解决诸如死锁,同步等问题。

总之,如果你不想你的公司被称为“皮包公司”,你就得多几个员工;

如果你不想让你的程序显得稚气,就在你的程序里引入多线程吧!

本文将对C#编程中的多线程机制进行探讨,通过一些实例解决对线程的控制,多线程间通讯等问题。

为了省去创建GUI那些繁琐的步骤,更清晰地逼近线程的本质,下面所有的程序都是控制台程序,程序最后的Console.ReadLine()是为了使程序中途停下来,以便看清楚执行过程中的输出。

好了,废话少说,让我们来体验一下多线程的C#吧!

操纵一个线程 任何程序在执行时,至少有一个主线程,下面这段小程序可以给读者一个直观的印象:

//SystemThread.csusingSystem;

usingSystem.Threading;

namespaceThreadTest{  classRunIt  {    [STAThread]    staticvoidMain(string[]args)    {Thread"

;

//给当前线程起名为"

SystemThread"

      Console.ReadLine();

    }  }}

编译执行后你看到了什么?

是的,程序将产生如下输出:

SystemThread'

sStatus:

Running在这里,我们通过Thread类的静态属性CurrentThread获取了当前执行的线程,对其Name属性赋值“SystemThread”,最后还输出了它的当前状态(ThreadState)。

所谓静态属性,就是这个类所有对象所公有的属性,不管你创建了多少个这个类的实例,但是类的静态属性在内存中只有一个。

很容易理解CurrentThread为什么是静态的——虽然有多个线程同时存在,但是在某一个时刻,CPU只能执行其中一个。

就像上面程序所演示的,我们通过Thread类来创建和控制线程。

注意到程序的头部,我们使用了如下命名空间:

usingSystem;

usingSystem.Threading;

在.netframeworkclasslibrary中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。

其中提供Thread类用于创建线程,ThreadPool类用于管理线程池等等,此外还提供解决了线程执行安排,死锁,线程间通讯等实际问题的机制。

如果你想在你的应用程序中使用多线程,就必须包含这个类。

Thread类有几个至关重要的方法,描述如下:

∙Start():

启动线程

∙Sleep(int):

静态方法,暂停当前线程指定的毫秒数

∙Abort():

通常使用该方法来终止一个线程

∙Suspend():

该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复。

∙Resume():

恢复被Suspend()方法挂起的线程的执行

下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。

线程入口使程序知道该让这个线程干什么事,在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。

打开你的VS.net,新建一个控制台应用程序(ConsoleApplication),下面这些代码将让你体味到完全控制一个线程的无穷乐趣!

//ThreadTest.csusingSystem;

namespaceThreadTest{  publicclassAlpha    {      publicvoidBeta()      {        while(true)        {          Console.WriteLine("

Alpha.Betaisrunninginitsownthread."

);

        }      }    };

    publicclassSimple    {      publicstaticintMain()      {        Console.WriteLine("

ThreadStart/Stop/JoinSample"

        AlphaoAlpha=newAlpha();

        ThreadoThread=newThread(newThreadStart(oAlpha.Beta));

        oThread.Start();

        while(!

oThread.IsAlive);

        Thread.Sleep

(1);

        oThread.Abort();

        oThread.Join();

        Console.WriteLine();

        Console.WriteLine("

Alpha.Betahasfinished"

        try        {          Console.WriteLine("

TrytorestarttheAlpha.Betathread"

          oThread.Start();

        }        catch(ThreadStateException)        {          Console.Write("

ThreadStateExceptiontryingtorestartAlpha.Beta."

          Console.WriteLine("

Expectedsinceabortedthreadscannotberestarted."

          Console.ReadLine();

        }        return0;

      }    }  }

这段程序包含两个类Alpha和Simple,在创建线程oThread时我们用指向Alpha.Beta()方法的初始化了ThreadStart代理(delegate)对象,当我们创建的线程oThread调用oThread.Start()方法启动时,实际上程序运行的是Alpha.Beta()方法:

AlphaoAlpha=newAlpha();

  ThreadoThread=newThread(newThreadStart(oAlpha.Beta));

  oThread.Start();

然后在Main()函数的while循环中,我们使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程oThread。

然后我们试图用Thread.Abort()方法终止线程oThread,注意后面的oThread.Join(),Thread.Join()方法使主线程等待,直到oThread线程结束。

你可以给Thread.Join()方法指定一个int型的参数作为等待的最长时间。

之后,我们试图用Thread.Start()方法重新启动线程oThread,但是显然Abort()方法带来的后果是不可恢复的终止线程,所以最后程序会抛出ThreadStateException异常。

程序最后得到的结果将如下图:

在这里我们要注意的是其它线程都是依附于Main()函数所在的线程的,Main()函数是C#程序的入口,起始线程可以称之为主线程,如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。

而所有的线程虽然在微观上是串行执行的,但是在宏观上你完全可以认为它们在并行执行。

读者一定注意到了Thread.ThreadState这个属性,这个属性代表了线程运行时状态,在不同的情况下有不同的值,于是我们有时候可以通过对该值的判断来设计程序流程。

ThreadState在各种情况下的可能取值如下:

∙Aborted:

线程已停止

∙AbortRequested:

线程的Thread.Abort()方法已被调用,但是线程还未停止

∙Background:

线程在后台执行,与属性Thread.IsBackground有关

∙Running:

线程正在正常运行

∙Stopped:

线程已经被停止

∙StopRequested:

线程正在被要求停止

∙Suspended:

线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)

∙SuspendRequested:

线程正在要求被挂起,但是未来得及响应

∙Unstarted:

未调用Thread.Start()开始线程的运行

∙WaitSleepJoin:

线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

上面提到了Background状态表示该线程在后台运行,那么后台运行的线程有什么特别的地方呢?

其实后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。

一旦一个进程所有的前台线程都终止后,CLR(通用语言运行环境)将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。

当线程之间争夺CPU时间时,CPU按照是线程的优先级给予服务的。

在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。

给一个线程指定优先级,我们可以使用如下代码:

//设定优先级为最低myThread.Priority=ThreadPriority.Lowest;

通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。

现在我们对怎样创建和控制一个线程已经有了一个初步的了解,下面我们将深入研究线程实现中比较典型的的问题,并且探讨其解决方法。

线程的同步和通讯——生产者和消费者

假设这样一种情况,两个线程同时维护一个队列,如果一个线程对队列中添加元素,而另外一个线程从队列中取用元素,那么我们称添加元素的线程为生产者,称取用元素的线程为消费者。

生产者与消费者问题看起来很简单,但是却是多线程应用中一个必须解决的问题,它涉及到线程之间的同步和通讯问题。

前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。

但是多线程环境下,可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

C#提供了一个关键字lock,它可以把一段代码定义为互斥段(criticalsection),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。

在C#中,关键字lock定义如下:

lock(expression)statement_block

expression代表你希望跟踪的对象,通常是对象引用。

一般地,如果你想保护一个类的实例,你可以使用this;

如果你希望保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

  下面是一个使用lock关键字的典型例子,我将在注释里向大家说明lock关键字的用法和用途:

//lock.csusingSystem;

internalclassAccount{  intbalance;

  Randomr=newRandom();

  internalAccount(intinitial)  {  balance=initial;

  }  internalintWithdraw(intamount)  {  if(balance<

0)  {    thrownewException("

NegativeBalance"

  }  //下面的代码保证在当前线程修改balance的值完成之前  //不会有其他线程也执行这段代码来修改balance的值  //因此,balance的值是不可能小于0的  lock(this)  {    Console.WriteLine("

Current    if(balance>

=amount)    {    Thread.Sleep(5);

    balance=balance-amount;

    returnamount;

    }    else    {    return0;

//transactionrejected    }  }  }  internalvoidDoTransactions()  {  for(inti=0;

i<

100;

i++)    Withdraw(r.Next(-50,100));

  }}internalclassTest{  staticinternalThread[]threads=newThread[10];

  publicstaticvoidMain()  {  Accountacc=newAccount(0);

  for(inti=0;

10;

i++)  {    Threadt=newThread(newThreadStart(acc.DoTransactions));

    threads[i]=t;

  }  for(inti=0;

i++)    threads[i].Name=i.ToString();

i++)    threads[i].Start();

  Console.ReadLine();

  }}

而多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。

  Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。

对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。

Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。

下面代码说明了使用Monitor锁定一个对象的情形:

......QueueoQueue=newQueue();

......Monitor.Enter(oQueue);

......//现在oQueue对象只能被当前线程操纵了Monitor.Exit(oQueue);

//释放锁

如上所示,当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。

为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-catch-finally结构中的finally代码块里。

对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息,其一是现在持有锁的线程的引用,其二是一个预备队列,队列中保存了已经准备好获取锁的线程,其三是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。

当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。

下面是一个展示如何使用lock关键字和Monitor类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。

这个例程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示,我将在注释中介绍该程序的精要所在。

用到的系统命名空间如下:

首先,我们定义一个被操作的对象的类Cell,在这个类里,有两个方法:

ReadFromCell()和WriteToCell。

消费者线程将调用ReadFromCell()读取cellContents的内容并且显示出来,生产者进程将调用WriteToCell()方法向cellContents写入数据。

publicclassCell{  intcellContents;

//Cell对象里边的内容  boolreaderFlag=false;

//状态标志,为true时可以读取,为false则正在写入  publicintReadFromCell()  {  lock(this)//Lock关键字保证了什么,请大家看前面对lock的介绍  {    if(!

readerFlag)//如果现在不可读取    {    try    {      Monitor.Wait(this);

    }    catch(SynchronizationLockExceptione)    {      Console.WriteLine(e);

    }    catch(ThreadInterruptedExceptione)    {      Console.WriteLine(e);

    }    }    Console.WriteLine("

Consume:

{0}"

cellContents);

    readerFlag=false;

    Monitor.Pulse(this);

  }  returncellContents;

  }  publicvoidWriteToCell(intn)  {  lock(this)  {    if(readerFlag)    {    try    {      Monitor.Wait(this);

    }    }    cellContents=n;

    Console.WriteLine("

Produce:

    readerFlag=true;

  }  }}

下面定义生产者CellProd和消费者类CellCons,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口。

publicclassCellProd{  Cellcell;

//被操作的Cell对象  intquantity=1;

//生产者生产次数,初始化为1  publicCellProd(Cellbox,intrequest)  {  //构造函数  cell=box;

  quantity=request;

  }  publicvoidThreadRun()  {  for(intlooper=1;

looper<

=quantity;

looper++)    cell.WriteToCell(looper);

  }}publicclassCellCons{  Cellcell;

  intquantity=1;

  publicCellCons(Cellbox,intrequest)  {  cell=box;

  }  publicvoidThreadRun()  {  intvalReturned;

  for(intlooper=1;

looper++)    valReturned=cell.ReadFromCell();

//消费者从操作对象中读取信息  }}

然后在下面这个类MonitorSample的Main()函数中我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。

publicclassMonitorSample{  publicstaticvoidMain(String[]args)  {  intresult=0;

  Cellcell=newCell();

  //下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次  CellProdprod=newCellProd(cell,20);

  CellConscons=newCellCons(cell,20);

  Threadproducer=newThread(newThrea

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 冶金矿山地质

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1