C#多线程.docx
- 文档编号:25191770
- 上传时间:2023-06-06
- 格式:DOCX
- 页数:36
- 大小:57KB
C#多线程.docx
《C#多线程.docx》由会员分享,可在线阅读,更多相关《C#多线程.docx(36页珍藏版)》请在冰豆网上搜索。
C#多线程
【转】多线程:
C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上)
本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始,希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细节,适用场合不会过多解释。
让我们来看看这几个类的关系图:
1.lock关键字
lock是C#关键词,它将语句块标记为临界区,确保当一个线程位于代码的临界区时,另一个线程不进入临界区。
如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
方法是获取给定对象的互斥锁,执行语句,然后释放该锁。
MSDN上给出了使用lock时的注意事项通常,
●应避免锁定public类型,否则实例将超出代码的控制范围。
●常见的结构lock(this)、lock(typeof(MyType))和lock("myLock")违反此准则。
1)如果实例可以被公共访问,将出现lock(this)问题。
2)如果MyType可以被公共访问,将出现lock(typeof(MyType))问题由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例。
微软现在建议不要使用lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。
3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现lock(“myLock”)问题。
这个问题和.NETFramework创建字符串的机制有关系,如果两个string变量值都是"myLock",在内存中会指向同一字符串对象。
最佳做法是定义private对象来锁定,或privatestatic对象变量来保护所有实例所共有的数据。
我们再来看看lock关键字的本质,lock关键字其实就是对Monitor类的Enter()和Exit()方法的封装,并通过try...catch...finally语句块确保在lock语句块结束后执行Monitor.Exit()方法,释放互斥锁。
2.Monitor类
Monitor类通过向单个线程授予对象锁来控制对对象的访问。
对象锁提供限制访问临界区的能力。
当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。
还可以使用Monitor来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
通过对lock关键字的分析我们知道,lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。
另外Monitor类还有几个常用的方法:
TryEnter()能够有效的解决长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间的等待。
比如我们可以设置一个等待时间boolgotLock=Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。
Wait()释放对象上的锁以便允许其他线程锁定和访问该对象。
在其他线程访问对象时,调用线程将等待。
脉冲信号用于通知等待线程有关对象状态的更改。
Pulse(),PulseAll()向一个或多个等待线程发送信号。
该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。
等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。
一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。
注意:
Pulse、PulseAll和Wait方法必须从同步的代码块内调用。
我们假定一种情景:
妈妈做蛋糕,小孩有点馋,妈妈每做好一块就要吃掉,妈妈做好一块后,告诉小孩蛋糕已经做好了。
下面的例子用Monitor类的Wait和Pulse方法模拟小孩吃蛋糕的情景。
C#代码
//仅仅是说明Wait和Pulse/PulseAll的例子
1.//逻辑上并不严密,使用场景也并不一定合适
2.classMonitorSample
3.{
4.privateintn=1;//生产者和消费者共同处理的数据
5.privateintmax=10000;
6.
7.privateobjectmonitor=newobject();
8.
9.publicvoidProduce()
10.{
11.lock(monitor)
12.{
13.for(;n<=max;n++)
14.{
15.Console.WriteLine("妈妈:
第"+n.ToString()+"块蛋糕做好了");
16.//Pulse方法不用调用是因为另一个线程中用的是Wait(object,int)方法
17.//该方法使被阻止线程进入了同步对象的就绪队列
18.//是否需要脉冲激活是Wait方法一个参数和两个参数的重要区别
19.//Monitor.Pulse(monitor);
20.//调用Wait方法释放对象上的锁并阻止该线程(线程状态为WaitSleepJoin)
21.//该线程进入到同步对象的等待队列,直到其它线程调用Pulse使该线程进入到就绪队列中
22.//线程进入到就绪队列中才有条件争夺同步对象的所有权
23.//如果没有其它线程调用Pulse/PulseAll方法,该线程不可能被执行
24.Monitor.Wait(monitor);---进入等待队列,等待对方的脉冲唤醒。
25.}
26.}
27.}
28.
29.publicvoidConsume()
30.{
31.lock(monitor)
32.{
33.while(true)
34.{
35.//通知等待队列中的线程锁定对象状态的更改,但不会释放锁
36.//接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中
37.//注意:
最终能获得锁的线程并不一定是得到Pulse脉冲的线程
38.Monitor.Pulse(monitor);
39.//释放对象上的锁并阻止当前线程,直到它重新获取该锁
40.//如果指定的超时间隔已过,则线程进入就绪队列
41.Monitor.Wait(monitor,1000);--继续进入就绪队列
42.Console.WriteLine("孩子:
开始吃第"+n.ToString()+"块蛋糕");
43.}
44.}
45.}
46.
47.staticvoidMain(string[]args)
48.{
49.MonitorSampleobj=newMonitorSample();
50.ThreadtProduce=newThread(newThreadStart(obj.Produce));
51.ThreadtConsume=newThread(newThreadStart(obj.Consume));
52.//Startthreads.
53.tProduce.Start();
54.tConsume.Start();
55.
56.Console.ReadLine();
57.}
58.}
//仅仅是说明Wait和Pulse/PulseAll的例子
//逻辑上并不严密,使用场景也并不一定合适
classMonitorSample
{
privateintn=1;//生产者和消费者共同处理的数据
privateintmax=10000;
privateobjectmonitor=newobject();
publicvoidProduce()
{
lock(monitor)
{
for(;n<=max;n++)
{
Console.WriteLine("妈妈:
第"+n.ToString()+"块蛋糕做好了");
//Pulse方法不用调用是因为另一个线程中用的是Wait(object,int)方法
//该方法使被阻止线程进入了同步对象的就绪队列
//是否需要脉冲激活是Wait方法一个参数和两个参数的重要区别
//Monitor.Pulse(monitor);
//调用Wait方法释放对象上的锁并阻止该线程(线程状态为WaitSleepJoin)
//该线程进入到同步对象的等待队列,直到其它线程调用Pulse使该线程进入到就绪队列中
//线程进入到就绪队列中才有条件争夺同步对象的所有权
//如果没有其它线程调用Pulse/PulseAll方法,该线程不可能被执行
Monitor.Wait(monitor);
}
}
}
publicvoidConsume()
{
lock(monitor)
{
while(true)
{
//通知等待队列中的线程锁定对象状态的更改,但不会释放锁
//接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中
//注意:
最终能获得锁的线程并不一定是得到Pulse脉冲的线程
Monitor.Pulse(monitor);
//释放对象上的锁并阻止当前线程,直到它重新获取该锁
//如果指定的超时间隔已过,则线程进入就绪队列
Monitor.Wait(monitor,1000);
Console.WriteLine("孩子:
开始吃第"+n.ToString()+"块蛋糕");
}
}
}
staticvoidMain(string[]args)
{
MonitorSampleobj=newMonitorSample();
ThreadtProduce=newThread(newThreadStart(obj.Produce));
ThreadtConsume=newThread(newThreadStart(obj.Consume));
//Startthreads.
tProduce.Start();
tConsume.Start();
Console.ReadLine();
}
}
这个例子的目的是要理解Wait和Pulse如何保证线程同步的,同时要注意Wait(obeject)和Wait(object,int)方法的区别,理解它们的区别很关键的一点是要理解同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。
本篇继续介绍WaitHandler类及其子类Mutex,ManualResetEvent,AutoResetEvent的用法。
.NET中线程同步的方式多的让人看了眼花缭乱,究竟该怎么去理解呢?
其实,我们抛开.NET环境看线程同步,无非是执行两种操作:
一是互斥/加锁,目的是保证临界区代码操作的“原子性”;另一种是信号灯操作,目的是保证多个线程按照一定顺序执行,如生产者线程要先于消费者线程执行。
.NET中线程同步的类无非是对这两种方式的封装,目的归根结底都可以归结为实现互斥/加锁或者是信号灯这两种方式,只是它们的适用场合有所不。
下面我们根据类的层次结构了解WaitHandler及其子类。
1.WaitHandler
WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,它封装Win32同步句柄内核对象,也就是说是这些内核对象的托管版本。
线程可以通过调用WaitHandler实例的方法WaitOne在单个等待句柄上阻止。
此外,WaitHandler类重载了静态方法,以等待所有指定的等待句柄都已收集到信号WaitAll,或者等待某一指定的等待句柄收集到信号WaitAny。
这些方法都提供了放弃等待的超时间隔、在进入等待之前退出同步上下文的机会,并允许其它线程使用同步上下文。
WaitHandler是C#中的抽象类,不能实例化。
2.EventWaitHandlervs.ManualResetEventvs.AutoResetEvent(同步事件)
我们先看看两个子类ManualResetEvent和AutoResetEvent在.NETFramework中的实现:
C#代码
1.//.NETFramework中ManualResetEvent类的实现
2.[ComVisible(true),HostProtection(SecurityAction.LinkDemand,Synchronization=true,ExternalThreading=true)]
3.publicsealedclassManualResetEvent:
EventWaitHandle
4.{
5.//Methods
6.publicManualResetEvent(boolinitialState):
base(initialState,EventResetMode.ManualReset)
7.{
8.}
9.}
10.
11.//.NETFramework中AutoResetEvent类的实现
12.[ComVisible(true),HostProtection(SecurityAction.LinkDemand,Synchronization=true,ExternalThreading=true)]
13.publicsealedclassAutoResetEvent:
EventWaitHandle
14.{
15.//Methods
16.publicAutoResetEvent(boolinitialState)
17.:
base(initialState,EventResetMode.AutoReset)
18.{
19.}
20.}
//.NETFramework中ManualResetEvent类的实现
[ComVisible(true),HostProtection(SecurityAction.LinkDemand,Synchronization=true,ExternalThreading=true)]
publicsealedclassManualResetEvent:
EventWaitHandle
{
//Methods
publicManualResetEvent(boolinitialState):
base(initialState,EventResetMode.ManualReset)
{
}
}
//.NETFramework中AutoResetEvent类的实现
[ComVisible(true),HostProtection(SecurityAction.LinkDemand,Synchronization=true,ExternalThreading=true)]
publicsealedclassAutoResetEvent:
EventWaitHandle
{
//Methods
publicAutoResetEvent(boolinitialState)
:
base(initialState,EventResetMode.AutoReset)
{
}
}
原来ManualResetEvent和AutoResetEvent都继承自EventWaitHandler,它们的唯一区别就在于父类EventWaitHandler的构造函数参数EventResetMode不同,这样我们只要弄清了参数EventResetMode值不同时,EventWaitHandler类控制线程同步的行为有什么不同,两个子类也就清楚了。
为了便于描述,我们不去介绍父类的两种模式,而直接介绍子类。
ManualResetEvent和AutoResetEvent的共同点:
1)Set方法将事件状态设置为终止状态,允许一个或多个等待线程继续;Reset方法将事件状态设置为非终止状态,导致线程阻止;WaitOne阻止当前线程,直到当前线程的WaitHandler收到事件信号。
2)可以通过构造函数的参数值来决定其初始状态,若为true则事件为终止状态从而使线程为非阻塞状态,为false则线程为阻塞状态。
3)如果某个线程调用WaitOne方法,则当事件状态为终止状态时,该线程会得到信号,继续向下执行。
ManualResetEvent和AutoResetEvent的不同点:
1)AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待,也就是说AutoResetEvent一次只唤醒一个线程;
2)ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。
3)也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。
示例场景:
张三、李四两个好朋友去餐馆吃饭,两个人点了一份宫爆鸡丁,宫爆鸡丁做好需要一段时间,张三、李四不愿傻等,都专心致志的玩起了手机游戏,心想宫爆鸡丁做好了,服务员肯定会叫我们的。
服务员上菜之后,张三李四开始享用美味的饭菜,饭菜吃光了,他们再叫服务员过来买单。
我们可以从这个场景中抽象出来三个线程,张三线程、李四线程和服务员线程,他们之间需要同步:
服务员上菜—>张三、李四开始享用宫爆鸡丁—>吃好后叫服务员过来买单。
这个同步用什么呢?
ManualResetEvent还是AutoResetEvent?
通过上面的分析不难看出,我们应该用ManualResetEvent进行同步,下面是程序代码:
张三李四吃饭的故事
C#代码
1.publicclassEventWaitTest
2.{
3.privatestringname;//顾客姓名
4.//privatestaticAutoResetEventeventWait=newAutoResetEvent(false);
5.privatestaticManualResetEventeventWait=newManualResetEvent(false);
6.privatestaticManualResetEventeventOver=newManualResetEvent(false);
7.
8.publicEventWaitTest(stringname)
9.{
10.this.name=name;
11.}
12.
13.publicstaticvoidProduct()
14.{
15.Console.WriteLine("服务员:
厨师在做菜呢,两位稍等");
16.Thread.Sleep(2000);
17.Console.WriteLine("服务员:
宫爆鸡丁好了");
18.eventWait.Set();
19.while(true)
20.{
21.if(eventOver.WaitOne(1000,false))
22.{
23.Console.WriteLine("服务员:
两位请买单");
24.eventOver.Reset();
25.}
26.}
27.}
28.
29.publicvoidConsume()
30.{
31.while(true)
32.{
33.if(eventWait.WaitOne(1000,false))
34.{
35.Console.WriteLine(this.name+":
开始吃宫爆鸡丁");
36.Thread.Sleep(2000);
37.Console.WriteLine(this.name+":
宫爆鸡丁吃光了");
38.eventWait.Reset();
39.eventOver.Set();
40.break;
41.}
42.else
43.{
44.Console.WriteLine(this.name+":
等着上菜无聊先玩会手机游戏");
45.}
46.}
47.}
48.}
49.
50.publicclassApp
51.{
52.publicstaticvoidMain(string[]args)
53.{
54.EventWaitTestzhangsan=newEventWaitTest("张三");
55.EventWaitTestlisi=newEventWaitTest("李四");
56.
57.Threadt1=newThread(newThreadStart(zhangsan.Consume));
58.Threadt2=newThread(newThreadStart(lisi.Consume));
59.Threadt3=newThread(newThreadStart(EventWaitTest.Product));
60.
61.t1.Start();
62.t2.Start();
63.t3.Start();
64.
65.Console.Read();
66.}
67.}
publicclassEventWaitTest
{
privatestringname;//顾
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# 多线程