归纳一下: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.

      如:

    复制代码
    Code
    public void Function() { object lockThis = new object (); lock (lockThis){ // Access thread-sensitive resources. }}
     
      
    复制代码

      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一个字符串

      三、System.Threading.Interlocked

      对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步,存在于System.Threading命名空间。Interlocked类有以下方法:Increment , Decrement , ExchangeCompareExchange 。使用IncrementDecrement 可以保证对一个整数的加减为一个原子操作。Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作也是按原子操作执行的。如:

    复制代码
    Code
    lock (x){DoSomething();}
     
      等效于

    object obj = ( object
    )x;
    System.Threading.Monitor.Enter(obj);
    try

    {
    DoSomething();
    }
    finally
    {
    System.Threading.Monitor.Exit(obj);
    }
     
      
    复制代码

    关于用法,请参考下面的代码:

    复制代码
    Code private static object m_monitorObject = new object ();[STAThread]static void Main( string [] args){Thread thread = new Thread( new ThreadStart(Do));thread.Name = " Thread1 " ;Thread thread2 = new Thread( new ThreadStart(Do));thread2.Name = " Thread2 " ;thread.Start();thread2.Start();thread.Join();thread2.Join();Console.Read();}static void Do(){if ( ! Monitor.TryEnter(m_monitorObject)){Console.WriteLine( " Can't visit Object " + Thread.CurrentThread.Name);return ;}try {Monitor.Enter(m_monitorObject);Console.WriteLine( " Enter Monitor " + Thread.CurrentThread.Name);Thread.Sleep( 5000 );}finally {Monitor.Exit(m_monitorObject);}}
     
      
    复制代码

      当线程1获取了m_monitorObject对象独占权时,线程2尝试调用TryEnter(m_monitorObject),此时会由于无法获取独占权而返回false,输出信息如下:

      另外,Monitor还提供了三个静态方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用来实现一种唤醒机制的同步。关于这三个方法的用法,可以参考MSDN,这里就不详述了。

      五、Mutex

      在使用上,Mutex与上述的Monitor比较接近,不过Mutex不具备Wait,Pulse,PulseAll的功能,因此,我们不能使用Mutex实现类似的唤醒的功能。不过Mutex有一个比较大的特点,Mutex是跨进程的,因此我们可以在同一台机器甚至远程的机器上的多个进程上使用同一个互斥体。尽管Mutex也可以实现进程内的线程同步,而且功能也更强大,但这种情况下,还是推荐使用Monitor,因为Mutex类是win32封装的,所以它所需要的互操作转换更耗资源。

      六、ReaderWriterLock

      在考虑资源访问的时候,惯性上我们会对资源实施lock机制,但是在某些情况下,我们仅仅需要读取资源的数据,而不是修改资源的数据,在这种情况下获取资源的独占权无疑会影响运行效率,因此.Net提供了一种机制,使用ReaderWriterLock进行资源访问时,如果在某一时刻资源并没有获取写的独占权,那么可以获得多个读的访问权,单个写入的独占权,如果某一时刻已经获取了写入的独占权,那么其它读取的访问权必须进行等待,参考以下代码:

    复制代码
    Code
    [System.Runtime.Remoting.Contexts.Synchronization]public class SynchronizedClass : System.ContextBoundObject{}
     
     

      八、MethodImplAttribute

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

    复制代码
    Code
    static
    AutoResetEvent autoEvent;static void DoWork(){Console.WriteLine(" worker thread started, now waiting on event" );autoEvent.WaitOne();Console.WriteLine(" worker thread reactivated, now exiting" );}[STAThread]static void Main(string [] args){autoEvent = new AutoResetEvent(false );Console.WriteLine("main thread starting worker thread" );Thread t = new Thread(new ThreadStart(DoWork));t.Start();Console.WriteLine("main thrad sleeping for 1 second" );Thread.Sleep(1000 );Console.WriteLine("main thread signaling worker thread" );autoEvent.Set();Console.ReadLine(); }
     
      
    复制代码

    我们先来看一下输出:

    在主函数中,首先创建一个AutoResetEvent的实例,参数false表示初始状态为非终止,如果是true的话,初始状态则为终止。然后创建并启动一个子线程,在子线程中,通过调用AutoResetEvent的WaitOne方法,使子线程等待指定事件的发生。然后主线程等待一秒后,调用AutoResetEvent的Set方法,使状态由非终止变为终止,重新激活子线程。

    参考:

    1.MSDN(http://msdn.microsoft.com/zh-cn/library/ms173179(VS.80).aspx )

    2.http://www.cnblogs.com/VincentWP/archive/2008/06/25/1229104.html

    转自:http://www.cnblogs.com/michaelxu/archive/2008/09/20/1293716.html

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值