好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

线程同步

线程同步

目录:

一、线程同步概述

二、线程同步的使用

三 、总结

一、线程同步概述

前面的文章都是讲创建多线程来实现让我们能够更好的响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享的资源的情况,在这种情况下,就需要我们用到线程同步,线程同步可以防止数据(共享资源)的损坏。

然而我们在设计应用程序还是要尽量避免使用线程同步, 因为线程同步会产生一些问题:

1. 它的使用比较繁琐。因为我们要用额外的代码把多个线程同时访问的数据包围起来,并获取和释放一个线程同步锁,如果我们在一个代码块忘记获取锁,就有可能造成数据损坏。

2. 使用线程同步会影响性能,获取和释放一个锁肯定是需要时间的吧,因为我们在决定哪个线程先获取锁时候, CPU必须进行协调,进行这些额外的工作就会对性能造成影响

3. 因为线程同步一次只允许一个线程访问资源,这样就会阻塞线程,阻塞线程会造成更多的线程被创建,这样CPU就有可能要调度更多的线程,同样也对性能造成了影响。

所以在实际的设计中还是要尽量避免使用线程同步,因此我们要避免使用一些共享数据,例如静态字段。

二、线程同步的使用

2.1 对于使用锁性能的影响

上面已经说过使用锁将会对性能产生影响, 下面通过比较使用锁和不使用锁时消耗的时间来说明这点

 using   System;
  using   System.Diagnostics;
  using   System.Threading;

  namespace   InterlockedSample
{
      //   比较使用锁和不使用锁锁消耗的时间
      //   通过时间来说明使用锁性能的影响 
     class   Program
    {
          static   void  Main( string  [] args)
        {
              int  x =  0  ;
              //   迭代次数为500万 
             const   int  iterationNumber =  5000000  ; 
              //   不采用锁的情况
              //   StartNew方法 对新的 Stopwatch 实例进行初始化,将运行时间属性设置为零,然后开始测量运行时间。 
            Stopwatch sw =  Stopwatch.StartNew();
              for  ( int  i =  0 ; i < iterationNumber; i++ )
            {
                x ++ ;
            }

            Console.WriteLine(  "  Use the all time is :{0} ms  "  , sw.ElapsedMilliseconds);

            sw.Restart();
              //   使用锁的情况 
             for  ( int  i =  0 ; i < iterationNumber; i++ )
            {
                Interlocked.Increment(  ref   x);
            }

            Console.WriteLine(  "  Use the all time is :{0} ms  "  , sw.ElapsedMilliseconds);
            Console.Read();
        }
    }
} 

运行结果(这是在我电脑上运行的结果)从结果中可以看出加了锁的运行速度慢了好多(慢了11倍 197/18 ):

2.2 Interlocked实现线程同步

Interlocked类提供了 为多个线程共享的变量提供原子操作,当我们在多线程中对一个整数进行递增操作时,就需要实现线程同步。

因为 增加变量操作(++运算符)不是一个原子操作,需要执行下列步骤:

1)将实例变量中的值加载到寄存器中。

2)增加或减少该值。

3)在实例变量中存储该值。

如果不使用  Interlocked.Increment 方法,线程可能会在执行完前两个步骤后被抢先。然后由另一个线程执行所有三个步骤,此时第一个线程还没有把变量的值存储到实例变量中去,而另一个线程就可以把实例变量加载到寄存器里面读取了(此时加载的值并没有改变),所以会导致出现的结果不是我们预期的,相信这样的解释可以帮助大家更好的理解 Interlocked.Increment 方法和 原子性操作,

下面通过一段代码来演示下加锁和不加锁的区别(开始讲过加锁会对性能产生影响,这里将介绍加锁来解决线程同步的问题,得到我们预期的结果):

不加锁的情况:

 class   Program
    {
          static   void  Main( string  [] args)
        {       for  ( int  i =  0 ; i <  10 ; i++ )
            {
                Thread testthread  =  new   Thread(Add);
                testthread.Start();
            }

            Console.Read();
        }

          //   共享资源 
         public   static   int  number =  1  ;

          public   static   void   Add()
        {
            Thread.Sleep(10  00  );
            Console.WriteLine(  "  the current value of number is:{0}  " , ++ number);
        }
} 

运行结果(不同电脑上可能运行的结果和我的不一样,但是都是得到不是预期的结果的):

为了解决这样的问题,我们可以通过使用  Interlocked.Increment 方法来实现原子的自增操作。

代码很简单,只需要把++number改成Interlocked.Increment( ref  number)就可以得到我们预期的结果了,在这里代码和运行结果就不贴了。

总之 Interlocked 类中的方法都是执行一次原子读取以及写入的操作的。

2.3 Monitor实现线程同步

对于上面那 个情况也可以通过 Monitor.Enter和Monitor.Exit方法来实现线程同步。C#中通过lock关键字来提供简化的语法(lock可以理解为Monitor.Enter和Monitor.Exit方法的语法糖),代码也很简单:

 using   System;
  using   System.Threading;

  namespace   MonitorSample
{
      class   Program
    {
          static   void  Main( string  [] args)
        {
              for  ( int  i =  0 ; i <  10 ; i++ )
            {
                Thread testthread  =  new   Thread(Add);
                testthread.Start();
            }

            Console.Read();
        }

          //   共享资源 
         public   static   int  number =  1  ;

          public   static   void   Add()
        {
            Thread.Sleep(  1000  );
              //  获得排他锁 
             Monitor.Enter(number);

            Console.WriteLine(  "  the current value of number is:{0}  " , number++ );

              //   释放指定对象上的排他锁。 
             Monitor.Exit(number);
        }
    }
} 

运行结果当然是我们所期望的:

在 Monitor类中还有其他几个方法在这里也介绍,只是让大家引起注意下,一个Wait方法,很明显Wait方法的作用是: 释放某个对象上的锁以便允许其他线程锁定和访问这个对象。第二个就是 TryEnter方法,这个方法与Enter方法主要的区别在于是否阻塞当前线程,当一个对象通过Enter方法获取锁,而没有执行Exit方法释放锁,当另一个线程想通过Enter获得锁时,此时该线程将会阻塞,直到另一个线程释放锁为止,而TryEnter不会阻塞线程。具体代码就不不写出来了。

2.4 ReaderWriterLock实现线程同步

如果我们需要对一个共享资源执行多次读取时,然而用前面所讲的类实现的同步锁都只允许一个线程允许,所有线程将阻塞,但是这种情况下肯本没必要堵塞其他线程, 应该让它们并发的执行,因为我们此时只是进行读取操作,此时通过ReaderWriterLock类可以很好的实现读取并行。

演示代码为:

 using   System;
  using   System.Collections.Generic;
  using   System.Threading;

  namespace   ReaderWriterLockSample
{
      class   Program
    {
          public   static  List< int > lists =  new  List< int > ();

          //   创建一个对象 
         public   static  ReaderWriterLock readerwritelock =  new   ReaderWriterLock();
          static   void  Main( string  [] args)
        {
              //  创建一个线程读取数据 
            Thread t1 =  new   Thread(Write);
            t1.Start();
              //   创建10个线程读取数据 
             for  ( int  i =  0 ; i <  10 ; i++ )
            {
                Thread t  =  new   Thread(Read);
                t.Start();
            }

            Console.Read();
                
        }

          //   写入方法 
         public   static   void   Write()
        {
              //   获取写入锁,以10毫秒为超时。 
            readerwritelock.AcquireWriterLock( 10  );
            Random ran  =  new   Random();
              int  count = ran.Next( 1 ,  10  );
            lists.Add(count);
            Console.WriteLine(  "  Write the data is:  "  +  count);
              //   释放写入锁 
             readerwritelock.ReleaseWriterLock();
        }

          //   读取方法 
         public   static   void   Read()
        {
              //   获取读取锁 
            readerwritelock.AcquireReaderLock( 10  );

              foreach  ( int  li  in   lists)
            {
                  //   输出读取的数据 
                 Console.WriteLine(li);
            }

              //   释放读取锁 
             readerwritelock.ReleaseReaderLock();
        }
    }
} 

运行结果:

 

三、总结

本文中主要介绍如何实现多线程同步的问题, 通过线程同步可以防止共享数据的损坏,但是由于获取锁的过程会有性能损失,所以在设计应用过程中尽量减少线程同步的使用。本来还要介绍 互斥(Mutex), 信号量(Semaphore), 事件构造的, 由于篇幅的原因怕影响大家的阅读,所以这剩下的内容放在后面介绍的。  

如果您看了本篇博客,觉得对你有帮助,请点击右下角的 [推荐] 
如果您想转载本博客,请注明出处 
如果您对本文有意见或者建议,欢迎留言 
感谢您的阅读,请关注我的后续博客

分类:  CLR

标签:  多线程同步 锁

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于线程同步的详细内容...

  阅读:41次