c# 线程同步系列(一)lock与Monitor的用法



1.Monitor.Wait方法
当线程调用 Wait 时,它释放对象的锁并进入对象的等待队列,对象的就绪队列中的下一个线程(如果有)获取锁并拥有对对象的独占使用。
Wait()就是交出锁的使用权,使线程处于阻塞状态,直到再次获得锁的使用权。

2.Monitor.Pulse方法
当前线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁。pulse()并不会使当前线程释放锁。


Monitor

    lock关键字比Monitor简洁,其实lock就是对MonitorEnterExit的一个封装。另外Monitor还有几个常用的方法:TryEnter能够有效的决绝长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEntermyobject,1000,让当前线程在等待1000秒后根据返回的bool 值来决定是否继续下面的操作。Pulse以及PulseAll还有Wait方法是成对使用的,它们能让你更精确的控制线程之间的并发,MSDN关于这3个方法的解释很含糊,有必要用一个具体的例子来说明一下:
using System.Threading;
public  class Program {
    static  object ball =  new  object();
    public  static  void Main() {
      Thread threadPing =  new Thread( ThreadPingProc );
      Thread threadPong =  new Thread( ThreadPongProc );
      threadPing.Start(); threadPong.Start();
      }
    static  void ThreadPongProc() {
      System.Console.WriteLine("ThreadPong: Hello!");
       lock ( ball )
          for ( int i = 0; i < 5; i++){
            System.Console.WriteLine("ThreadPong: Pong ");
            Monitor.Pulse( ball );
            Monitor.Wait( ball );
         }
      System.Console.WriteLine("ThreadPong: Bye!");
   }
    static  void ThreadPingProc() {
      System.Console.WriteLine("ThreadPing: Hello!");
       lock ( ball )
          for( int i=0; i< 5; i++){
            System.Console.WriteLine("ThreadPing: Ping ");
            Monitor.Pulse( ball );
            Monitor.Wait( ball );
         }
      System.Console.WriteLine("ThreadPing: Bye!");
   }
}
   执行结果如下(有可能是ThreadPong先执行):
ThreadPing: Hello!
ThreadPing: Ping
ThreadPong: Hello!
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Bye!

   当threadPing进程进入ThreadPingProc锁定ball并调用Monitor.Pulse( ball );后,它通知threadPong从阻塞队列进入准备队列,当threadPing调用Monitor.Wait( ball )阻塞自己后,它放弃了了对ball的锁定,所以threadPong得以执行。PulseAll与Pulse方法类似,不过它是向所有在阻塞队列中的进程发送通知信号,如果只有一个线程被阻塞,那么请使用Pulse方法。

Lock使用的建议

  关于使用Lock微软给出的一些建议。你能够在MSDN上找到这么一段话:

  通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
  1.如果实例可以被公共访问,将出现 lock (this) 问题。
  2.如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
   3.由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock("myLock") 问题。
  4.最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

  lock(this)的问题我是这么理解:

  1. 处于某种原因Account在整个程序空间内不是唯一,那么不同Account实例的相应方法就不可能互斥,因为他们请求的是不同Accout实例内部的不同的锁。这时候微软示例中的private Object thisLock仍然也避免不了这个问题,而需要使用private static Object thisLock来解决问题,因为static变量是所有类实例共享的。
  2. 猜想就算Account只有一个实例,但是如果在程序内部被多个处理不同任务的线程访问,那么Account实例可能会被某段代码直接作为锁锁定;这相当于你自己锁定了自己,而别人在不告诉你的情况下也可以能锁定你。这些情况都是你在写Account这个类的时候并没有办法作出预测的,所以你的 Withdraw代码可能被挂起,在多线程的复杂情况下也容易造成死锁。不管怎样,你写这段代码的时候肯定不会期待外部的代码跟你使用了同一把锁吧?这样很危险。另外,从面向对象来说,这等于把方法内部的东西隐式的暴露出去。为了实现互斥,专门建立不依赖系this的代码机制总是好的;thisLock,专事专用,是个好习惯。

   MyType的问题跟lock(this)差不多理解,不过比lock(this)更严重。因为Lock(typeof(MyType))锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类对象(就是拥有Static成员的那个对象实例),锁定它就锁定了该对象的所有实例。同时 lock(typeof(MyType))是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们都有可能锁定类对象,完全阻止你代码的执行,导致你自己代码的挂起或者死锁。

  至于lock("myLock"),是因为在.NET中字符串会被暂时存放。如果两个变量的字符串内容相同的话,.NET会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用lock(“my lock”)的话,它们实际锁住的是同一个对象。

.


临界区&Monitor

 

监视器(Monitor)的概念

  可以在MSDN(http://msdn.microsoft.com/zh-cn/library/ms173179(VS.80).aspx)上找到下面一段话:

与lock关键字类似,监视器防止多个线程同时执行代码块。Enter方法允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用Exit。这与使用lock关键字一样。事实上,lock 关键字就是用Monitor 类来实现的。例如:

lock(x)
{
  DoSomething();
}

这等效于:

System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
  DoSomething();
}
finally
{
  System.Threading.Monitor.Exit(obj);
}

使用 lock 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过 finally 关键字来实现的,无论是否引发异常它都执行关联的代码块。

  

Monitor中和lock等效的方法

  Monitor是一个静态类,因此不能被实例化,只能直接调用Monitor上的各种方法来完成与lock相同的功能:

  • Enter(object)/TryEnter(object)/TryEnter(object, int32)/TryEnter(object, timespan):用来获取对象锁(Lock中已经提到过,这里再强调一次,是对象类型而不能是值类型),标记临界区的开始。与Enter不同,TryEnter永远不会阻塞代码,当无法获取对象锁时它会返回False,并且调用者不进入临界区。TryEnter还有两种重载,可以定义一个时间段,在该时间段内一直尝试获得对象锁,超时则返回False。
  • Exit(object):没啥好说的,释放对象锁、退出临界区。只是一定记得在try的finally块里调用,否则一但由于异常造成Exit无法执行,对象锁得不到释放,就会造成死锁。此外,调用Exit的线程必须拥有 object 参数上的锁,否则会引发SynchronizationLockException异常。在调用线程获取指定对象上的锁后,可以重复对该对象进行了相同次数的 Exit 和 Enter 调用;如果调用 Exit 与调用 Enter 的次数不匹配,那么该锁不会被正确释放。

  上篇中提到的有关lock的所有使用方法和建议,都适用于它们。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值