C#线程锁(上)
2009-06-22 18:01
99 查看
本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始,希 望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细节,适用场合不会过多解释。让我们来看看这几个类的关系图:
lock (lockobject)
IL_0045: call void [mscorlib]System.Threading.Monitor::Enter(object)
IL_004a: nop
.try
{
IL_004b: nop
IL_004c: ldc.i4.5
IL_004d: stloc.1
IL_004e: nop
IL_004f: leave.s IL_0059
} // end .try
finally
{
IL_0051: ldloc.3
IL_0052: call void [mscorlib]System.Threading.Monitor::Exit(object)
IL_0057: nop
IL_0058: endfinally
} // end handler
通过上面的代码我们很清楚的看到: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,可以有效防止死锁或者长时间 的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。
Wait()释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。
Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被 放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。
注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。
我们假定一种情景:妈妈做蛋糕,小孩有点馋,妈妈每做好一块就要吃掉,妈妈做好一块后,告诉小孩蛋糕已经做好了。下面的例子用Monitor类的Wait和Pulse方法模拟小孩吃蛋糕的情景。
Wait和Pulse的示例
//仅仅是说明Wait和Pulse/PulseAll的例子
//逻辑上并不严密,使用场景也并不一定合适
class MonitorSample
{
private int n = 1; //生产者和消费者共同处理的数据
private int max = 10000;
private object monitor = new object();
public void Produce()
{
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);
}
}
}
public void Consume()
{
lock (monitor)
{
while (true)
{
//通知等待队列中的线程锁定对象状态的更改,但不会释放锁
//接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中
//注意:最终能获得锁的线程并不一定是得到Pulse脉冲的线程
Monitor.Pulse(monitor);
//释放对象上的锁并阻止当前线程,直到它重新获取该锁
//如果指定的超时间隔已过,则线程进入就绪队列
Monitor.Wait(monitor,1000);
Console.WriteLine("孩子:开始吃第" + n.ToString() + "块蛋糕");
}
}
}
static void Main(string[] args)
{
MonitorSample obj = new MonitorSample();
Thread tProduce = new Thread(new ThreadStart(obj.Produce));
Thread tConsume = new Thread(new ThreadStart(obj.Consume));
//Start threads.
tProduce.Start();
tConsume.Start();
Console.ReadLine();
}
}
这个例子的目的是要理解Wait和Pulse如何保证线程同步的,同时要注意Wait(obeject)和Wait(object,int)方法的区别,理解它们的区别很关键的一点是要理解同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。
lock (lockobject)
IL_0045: call void [mscorlib]System.Threading.Monitor::Enter(object)
IL_004a: nop
.try
{
IL_004b: nop
IL_004c: ldc.i4.5
IL_004d: stloc.1
IL_004e: nop
IL_004f: leave.s IL_0059
} // end .try
finally
{
IL_0051: ldloc.3
IL_0052: call void [mscorlib]System.Threading.Monitor::Exit(object)
IL_0057: nop
IL_0058: endfinally
} // end handler
通过上面的代码我们很清楚的看到: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,可以有效防止死锁或者长时间 的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。
Wait()释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。
Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被 放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。
注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。
我们假定一种情景:妈妈做蛋糕,小孩有点馋,妈妈每做好一块就要吃掉,妈妈做好一块后,告诉小孩蛋糕已经做好了。下面的例子用Monitor类的Wait和Pulse方法模拟小孩吃蛋糕的情景。
Wait和Pulse的示例
//仅仅是说明Wait和Pulse/PulseAll的例子
//逻辑上并不严密,使用场景也并不一定合适
class MonitorSample
{
private int n = 1; //生产者和消费者共同处理的数据
private int max = 10000;
private object monitor = new object();
public void Produce()
{
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);
}
}
}
public void Consume()
{
lock (monitor)
{
while (true)
{
//通知等待队列中的线程锁定对象状态的更改,但不会释放锁
//接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中
//注意:最终能获得锁的线程并不一定是得到Pulse脉冲的线程
Monitor.Pulse(monitor);
//释放对象上的锁并阻止当前线程,直到它重新获取该锁
//如果指定的超时间隔已过,则线程进入就绪队列
Monitor.Wait(monitor,1000);
Console.WriteLine("孩子:开始吃第" + n.ToString() + "块蛋糕");
}
}
}
static void Main(string[] args)
{
MonitorSample obj = new MonitorSample();
Thread tProduce = new Thread(new ThreadStart(obj.Produce));
Thread tConsume = new Thread(new ThreadStart(obj.Consume));
//Start threads.
tProduce.Start();
tConsume.Start();
Console.ReadLine();
}
}
这个例子的目的是要理解Wait和Pulse如何保证线程同步的,同时要注意Wait(obeject)和Wait(object,int)方法的区别,理解它们的区别很关键的一点是要理解同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。
相关文章推荐
- C#线程调用带参数的方法
- Mark:使用线程处理(C# 编程指南)
- VS2005 C#创建WebService使用线程
- c# 线程第四部分:高级话题3
- C#笔记22:多线程之停止或取消线程
- C# 线程 在 sleep,suspend 之后 Abort 的方法
- c#如何实现一个线程暂停,等待用户输入文本后继续运行?
- 关于线程的总结(.net,c#,winform)
- C#线程调用
- C#中的线程(四)高级话题
- C# -- 线程 带参数 以及回调实现
- c# 跨线程修改控件
- c#线程操作控件
- C#后台线程及UI线程交互可以采用BackgroundWorker
- C#关于进程与线程(一)
- C# 在自定义线程中采用系统委托方式操作主窗体线程控件的方法
- [深入学习C#]C#实现多线程的方法:线程(Thread类)和线程池(ThreadPool)
- 在C#中如何判断线程当前所处的状态
- C# Winform 跨线程更新UI控件常用方法总结(转)
- 黄聪:C#“多线程线程间操作无效: 从不是创建控件的线程访问它。”,跨线程修改控件属性解决方案