• C# 线程同步


    volatile关键字

      volatile是最简单的一种同步方法,当然简单是要付出代价的。它只能在变量一级做同步,volatile的含义就是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。(【转自www.bitsCN.com 】)因此,当多线程同时访问该变量时,都将直接操作主存,从本质上做到了变量共享。

      能够被标识为volatile的必须是以下几种类型:(摘自MSDN)

    Any reference type.
    Any pointer type (in an unsafe context).
    The types sbyte, byte, short, ushort, int, uint, char, float, bool.
    An enum type with an enum base type of byte, sbyte, short, ushort, int, or uint.

    lock关键字

      lock是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。它可以保证当一个线程在关键代码段的时候,另一个线程不会进来,它只能等待,等到那个线程对象被释放,也就是说线程出了临界区。用法:

      lock的参数必须是基于引用类型的对象,不要是基本类型像bool,int什么的,这样根本不能同步,原因是lock的参数要求是对象,如果传入int,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。特别是不要使用字符串作为lock的参数,因为字符串被CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。建议使用不被“暂留”的私有或受保护成员作为参数。其实某些类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。
      所以,使用lock应该注意以下几点: 

      1、如果一个类的实例是public的,最好不要lock(this)。因为使用你的类的人也许不知道你用了lock,如果他new了一个实例,并且对这个实例上锁,就很容易造成死锁。

      2、如果MyType是public的,不要lock(typeof(MyType))

      3、永远也不要lock一个字符串

    Monitor

      Monitor类提供了与lock类似的功能,不过与lock不同的是,它能更好的控制同步块,当调用了Monitor的Enter(Object o)方法时,会获取o的独占权,直到调用Exit(Object o)方法时,才会释放对o的独占权,可以多次调用Enter(Object o)方法,只需要调用同样次数的Exit(Object o)方法即可,Monitor类同时提供了TryEnter(Object o,[int])的一个重载方法,该方法尝试获取o对象的独占权,当获取独占权失败时,将返回false。

      但使用 lock 通常比直接使用 Monitor 更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过 finally 中调用Exit来实现的。事实上,lock 就是用 Monitor 类来实现的。下面两段代码是等效的:

    Code 
    lock (x)
    {
    DoSomething();
    } 
    
    等效于
    
    object obj = ( object )x;
    System.Threading.Monitor.Enter(obj);
    try 
    {
    DoSomething();
    }
    finally 
    {
    System.Threading.Monitor.Exit(obj);
    }
    

    MethodImplAttribute

      如果整个方法内部的代码都需要上锁的话,使用MethodImplAttribute属性会更简单一些。这样就不用在方法内部加锁了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空间System.Runtime.CompilerServices 里面。但要注意这个属性会使整个方法加锁,直到方法返回,才释放锁。因此使用上不太灵活。如果要提前释放锁,则应该使用Monitor或lock。我们来看一个例子:

      Code
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void DoSomeWorkSync()
    {
    Console.WriteLine( " DoSomeWorkSync() -- Lock held by Thread " + 
    Thread.CurrentThread.GetHashCode());
    Thread.Sleep( 1000 );
    Console.WriteLine( " DoSomeWorkSync() -- Lock released by Thread " + 
    Thread.CurrentThread.GetHashCode());
    }
    public void DoSomeWorkNoSync()
    {
    Console.WriteLine( " DoSomeWorkNoSync() -- Entered Thread is " + 
    Thread.CurrentThread.GetHashCode());
    Thread.Sleep( 1000 );
    Console.WriteLine( " DoSomeWorkNoSync() -- Leaving Thread is " + 
    Thread.CurrentThread.GetHashCode());
    }
    
    [STAThread]
    static void Main( string [] args)
    {
    MethodImplAttr testObj = new MethodImplAttr();
    Thread t1 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
    Thread t2 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
    t1.Start();
    t2.Start();
    Thread t3 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
    Thread t4 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
    t3.Start();
    t4.Start();
    
    Console.ReadLine();
    }
    

    这里,我们有两个方法,我们可以对比一下,一个是加了属性MethodImpl的DoSomeWorkSync(),一个是没加的DoSomeWorkNoSync()。在方法中Sleep(1000)是为了在第一个线程还在方法中时,第二个线程能够有足够的时间进来。对每个方法分别起了两个线程,我们先来看一下结果:

    可以看出,对于线程1和2,也就是调用没有加属性的方法的线程,当线程2进入方法后,还没有离开,线程1有进来了,这就是说,方法没有同步。我们再来看看线程3和4,当线程3进来后,方法被锁,直到线程3释放了锁以后,线程4才进来。

  • 相关阅读:
    codna的安装与使用
    ubuntu 下修改MySQL 的root用户密码
    python中的排序
    CF 543C Remembering Strings
    CF 1119C Ramesses and Corner Inversion
    HihoCoder 1384 Genius ACM
    BZOJ3032 七夕祭
    Vjudge Code
    CF51C Three Base Stations
    Sumdiv POJ 1845
  • 原文地址:https://www.cnblogs.com/bincoding/p/8944016.html
Copyright © 2020-2023  润新知