C+多线程机制探讨Word文件下载.docx
- 文档编号:8448677
- 上传时间:2023-05-11
- 格式:DOCX
- 页数:42
- 大小:115.46KB
C+多线程机制探讨Word文件下载.docx
《C+多线程机制探讨Word文件下载.docx》由会员分享,可在线阅读,更多相关《C+多线程机制探讨Word文件下载.docx(42页珍藏版)》请在冰点文库上搜索。
任何程序在执行时,至少有一个主线程,下面这段小程序可以给读者一个直观的印象:
//SystemThread.cs
usingSystem;
usingSystem.Threading;
namespaceThreadTest
{
classRunIt
{
[STAThread]
staticvoidMain(string[]args)
{
Thread.CurrentThread.Name="
SystemThread"
;
//给当前线程起名为"
Console.WriteLine(Thread.CurrentThread.Name+"
'
Status:
"
+Thread.CurrentThread.ThreadState);
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()方法挂起的线程的执行
1
下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。
线程入口使程序知道该让这个线程干什么事,在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。
打开你的VS.net,新建一个控制台应用程序(ConsoleApplication),下面这些代码将让你体味到完全控制一个线程的无穷乐趣!
//ThreadTest.cs
namespaceThreadTest
{
publicclassAlpha
publicvoidBeta()
{
while(true)
{
Console.WriteLine("
Alpha.Betaisrunninginitsownthread."
);
}
}
};
publicclassSimple
publicstaticintMain()
Console.WriteLine("
ThreadStart/Stop/JoinSample"
AlphaoAlpha=newAlpha();
file:
//这里创建一个线程,使之执行Alpha类的Beta()方法
ThreadoThread=newThread(newThreadStart(oAlpha.Beta));
oThread.Start();
while(!
oThread.IsAlive);
Thread.Sleep
(1);
oThread.Abort();
oThread.Join();
Console.WriteLine();
Alpha.Betahasfinished"
try
TrytorestarttheAlpha.Betathread"
oThread.Start();
catch(ThreadStateException)
Console.Write("
ThreadStateExceptiontryingtorestartAlpha.Beta."
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.cs
internalclassAccount
intbalance;
Randomr=newRandom();
internalAccount(intinitial)
balance=initial;
internalintWithdraw(intamount)
if(balance<
0)
{
file:
//如果balance小于0则抛出异常
thrownewException("
NegativeBalance"
}
//下面的代码保证在当前线程修改balance的值完成之前
//不会有其他线程也执行这段代码来修改balance的值
//因此,balance的值是不可能小于0的
lock(this)
Console.WriteLine("
CurrentThread:
+Thread.CurrentThread.Name);
//如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
//另外一个线程却执行了balance=balance-amount修改了balance的值
//而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
//但是,这个线程却继续执行balance=balance-amount,所以导致balance可能小于0
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);
10;
Threadt=newThread(newThreadStart(acc.DoTransactions));
threads[i]=t;
threads[i].Name=i.ToString();
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
{
file:
//等待WriteToCell方法中调用Monitor.Pulse()方法
Monitor.Wait(this);
}
catch(SynchronizationLockExceptione)
Console.WriteLine(e);
catch(ThreadInterruptedExceptione)
Consume:
{0}"
cellContents);
readerFlag=false;
file:
//重置readerFlag标志,表示消费行为已经完成
Monitor.Pulse(this);
//通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
returncellContents;
publicvoidWriteToCell(intn)
lock(this)
if(readerFlag)
//当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
catch(Thr
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多线程 机制 探讨