好得很程序员自学网

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

NET多线程探索线程同步和通信

NET多线程探索线程同步和通信

NET多线程探索-线程同步和通信

2012-03-20 16:53 by 海不是蓝, 426 visits,  收藏 ,  编辑

NET中各种线程同步方法


在NET多线程开发中,有时候需要多个线程协调工作,完成这个步骤的过程称为“同步”。 

使用同步的主要原因: 
1.多个线程访问同一个共享资源。 
2.多线程写入文件时保证只有一个线程使用文件资源。 3.由事件引发线程,线程等待事件,需要挂起线程。 

NET中线程同步常见的几种方法: 

1.lock


lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。 
lock的优点:简单易用,对象的同步几乎透明,轻量级。 

使用lock需要注意: 
锁定的对于应该是私有的,如果是公有的对象,可能出现超出控制范围的其它代码锁定该对象。 
所以应该尽量避免使用lock(this),不保证会有其他线程闯入破坏数据正确性。 

一个lock(this)错误的示例:

 class   Program
 {
     static void  Main( string [] args)
    {
         A  a1 =  new   A ();
         A  a2 =  new   A ();
         Thread  T1 =  new   Thread ( new   ParameterizedThreadStart (a1.Test));
         Thread  T2 =  new   Thread ( new   ParameterizedThreadStart (a2.Test));
        T1.Start(2);
        T2.Start(5);
         Console .Read();
    }
}
 public class   A
 {
     public static   Int32  Count = 2;
     public void  Test( object  i)
    {
         lock  ( this )
        {
             Console .WriteLine( "Count={0}" , Count);
            Count += ( Int32 )i;
             Thread .Sleep(1 * 1000);
             Console .WriteLine( "i={0},?Count+i={1}" , i, Count);
             Console .WriteLine( "--------------------------" );
        }
    }
}

 
这里数据和我们期待的不相同。

这里lock锁定的是A的实例,当线程T1执行的时候,lock锁定了a1实例,所以线程t2执行a2实例虽然可以执行Test方法。

正确的写法:

 public class   A
 {
     private static object  obj =  new object ();
     public static   Int32  Count = 2;
     public void  Test( object  i)
    {
         lock  (obj)
        {
             Console .WriteLine( "Count={0}" , Count);
            Count += ( Int32 )i;
             Thread .Sleep(1 * 1000);
             Console .WriteLine( "i={0},?Count+i={1}" , i, Count);
             Console .WriteLine( "--------------------------" );
        }
    }
}

这里的线程已经正常工作了,Count也正常的累加。


lock (obj)怎么错误了? 
上面正常运行的程序稍微改动下!

 class   Program
 {
     static void  Main( string [] args)
    {
         A  a1 =  new   A ();
         A  a2 =  new   A ();
         Thread  T1 =  new   Thread ( new   ParameterizedThreadStart (a1.Test));
         Thread  T2 =  new   Thread ( new   ParameterizedThreadStart (a2.Test));
        T1.Start(2);
        T2.Start(5);
         Console .Read();
    }
}
 public class   A
 {
     private object  obj =  new object ();
     public static   Int32  Count = 2;
     public void  Test( object  i)
    {
         lock  (obj)
        {
             Console .WriteLine( "Count={0}" , Count);
            Count += ( Int32 )i;
             Thread .Sleep(1 * 1000);
             Console .WriteLine( "i={0},?Count+i={1}" , i, Count);
             Console .WriteLine( "--------------------------" );
        }
    }
}

分析下:这里我们把

 private static object  obj =  new object ();

中的static去掉了,所以obj变成了私有的实例成员,a1,a2都有不同的obj实例,所以lock这里也就没什么作用了! 

让lock不再错下去!  
这里我们也不再把static写上去了,只需要把调用那里稍微改动下。

 class   Program
 {
     static void  Main( string [] args)
    {
         A  a1 =  new   A ();
         Thread  T1 =  new   Thread ( new   ParameterizedThreadStart (a1.Test));
         Thread  T2 =  new   Thread ( new   ParameterizedThreadStart (a1.Test));
        T1.Start(2);
        T2.Start(5);
         Console .Read();
    }
}

我们这里让a2对象去见马克思了,a1对象的Test调用了2次,这里lock锁定的obj都是a1的同一个私有对象obj,所以lock是起作用的!

Monitor


Monitor实现同步比lock复杂点,lock实际上是Monitor的简便方式,lock最终还是编译成Monitor。 
不同处: 
1.Monitor在使用的时候需要手动指定锁和手动释放手。 
2.Monitor比lock多了几个实用的方法

 public static bool  Wait( object  obj);
 public static void  Pulse( object  obj);
 public static void  PulseAll( object  obj);


Mointor同步实例:

 class   Program
 {
     static void  Main( string [] args)
    {
         Int32 [] nums = { 1, 2, 3, 4, 5 };
         SumThread  ST1 =  new   SumThread ( "Thread1" );
         SumThread  ST2 =  new   SumThread ( "Thread2" );
        ST1.Run(nums);
        ST2.Run(nums);
         Console .Read();
    }
}

 public class   SumThread
 {
     //注意这里私有静态SA是SumThread共有的,所以考虑同步问题
      private static   SumArray  SA =  new   SumArray ();
     private   Thread  t;

     public  SumThread( string  ThreadName)
    {
        t =  new   Thread (( object  nums) =>
        {
             Console .WriteLine( "线程{0}开始执行..." , t.Name);
             Int32  i = SA.GetSum(( Int32 [])nums);
             Console .WriteLine( "线程{0}执行完毕,sum={1}" , t.Name, i);
        });
        t.Name = ThreadName;
    }

     public void  Run( Int32 [] nums)
    {
        t.Start(nums);
    }
}

 public class   SumArray
 {
     private   Int32  sum;
     private  object  obj =  new object ();

     public   Int32  GetSum( Int32 [] nums)
    {
         Monitor .Enter(obj);
         try
         {
             //初始化sum值,以免获取到其它线程的脏数据
             sum = 0;
             foreach  ( Int32  num  in  nums)
            {
                sum += num;
                 Console .WriteLine( "当前线程是:{0},sum={1}" ,  Thread .CurrentThread.Name, sum);
                 Thread .Sleep(1 * 1000);
            }
             return  sum;
        }
         catch  ( Exception  e)
        {
             Console .WriteLine(e.Message);
             return  0;
        }
         finally
         {
             //很重要,千万不要忘记释放锁
              Monitor .Exit(obj);
        }
    }
}


这里线程之间和蔼可亲的执行着,没有去抢着计算。 
试着把

 Monitor .Enter(obj);
 Monitor .Exit(obj);

去掉,那么线程就不会这么听话了!

另外一种同步的写法

前面使用的同步方式并不是所有情况都适合的,假如我们调用的是第三方的组件,我们没有修改源码的权限,那么我们只有考虑下面这种同步的实现。

 class   Program
 {
     static void  Main( string [] args)
    {
         Int32 [] nums = { 1, 2, 3, 4, 5 };
         SumThread  ST1 =  new   SumThread ( "Thread1" );
         SumThread  ST2 =  new   SumThread ( "Thread2" );
        ST1.Run(nums);
        ST2.Run(nums);
         Console .Read();
    }
}

 public class   SumThread
 {
     //注意这里私有静态SA是SumThread共2有的,所以考虑同步问题
      private static   SumArray  SA =  new   SumArray ();
     private   Thread  t;

     public  SumThread( string  ThreadName)
    {
        t =  new   Thread (( object  nums) =>
        {
             Console .WriteLine( "线程{0}开始执行..." , t.Name);
             Monitor .Enter(SA);
             try
             {
                 Int32  i = SA.GetSum(( Int32 [])nums);
                 Console .WriteLine( "线程{0}执行完毕,sum={1}" , t.Name, i);
            }
             catch  ( Exception  e)
            {
                 Console .WriteLine(e.Message);
            }
             finally
             {
                 //很重要,千万不要忘记释放锁
                  Monitor .Exit(SA);
            }
        });
        t.Name = ThreadName;
    }

     public void  Run( Int32 [] nums)
    {
        t.Start(nums);
    }
}

 public class   SumArray
 {
     private   Int32  sum;

     public   Int32  GetSum( Int32 [] nums)
    {

         //初始化sum值,以免获取到其它线程的脏数据
         sum = 0;
         foreach  ( Int32  num  in  nums)
        {
            sum += num;
             Console .WriteLine( "当前线程是:{0},sum={1}" ,  Thread .CurrentThread.Name, sum);
             Thread .Sleep(1 * 1000);
        }
         return  sum;
    }
}

注意:这里锁定的是SA.GetSum()调用本身,不是方法内部代码,SA对象是私有的。

Monitor-线程通信


当线程T正在lock块执行,需要访问另外一个线程lock块中的资源R,这个时候如果T等待R可用,有可能会让线程进入死循环。 
这里就可以使用线程通信,先让T暂时放弃对lock块中的控制,等R变得可用,那么就通知线程T恢复运行。

 public static bool  Wait( object  obj);
 public static void  Pulse( object  obj);
 public static void  PulseAll( object  obj);

上面就是这里需要用的方法,属于Monitor。 Wait是让线程暂停,这个方法有个重写,多了一个参数指定暂停的毫秒数。 
Pulse是唤醒线程。 
时钟滴答例子:

 class   Program
 {
     static void  Main( string [] args)
    {
         Clock  C =  new   Clock ();
        C.RunClock(1);
         Console .Read();
    }
}
 public class   Clock
 {
     private object  obj =  new object ();

     //开始运行时钟,输入运行分钟
      public void  RunClock( Int32  Minute)
    {
         Thread  T1 =  new   Thread (( object  Minute1) =>
        {
             Int32  m =  Convert .ToInt32(Minute1) * 60 / 2;
             while  (m > 0)
            {
                DI( true );

                m--;
            }
        });
         Thread  T2 =  new   Thread (( object  Minute1) =>
        {
             Int32  m =  Convert .ToInt32(Minute1) * 60 / 2;
             while  (m > 0)
            {
                DA( true );
                m--;
            }
        });
        T1.Start( true );
        T2.Start( true );
    }

     public void  DI( bool  run)
    {
         lock  (obj)
        {
             if  (!run)
            {
                 //不运行,唤醒其它锁定obj的线程
                  Monitor .Pulse(obj);
                 return ;
            }
             else
             {
                 Console .WriteLine( "嘀" );
                 Thread .Sleep(1000);
                 Monitor .Pulse(obj); //执行完毕,唤醒其它线程
                  Monitor .Wait(obj); //进入暂停,移交执行权利,等待唤醒
             }
        }
    }

     public void  DA( bool  run)
    {
         lock  (obj)
        {
             if  (!run)
            {
                 //不运行,唤醒其它锁定obj的线程
                  Monitor .Pulse(obj);
                 return ;
            }
             else
             {
                 Console .WriteLine( "嗒" );
                 Thread .Sleep(1000);
                 Monitor .Pulse(obj); //执行完毕,唤醒其它线程
                  Monitor .Wait(obj); //进入暂停,移交执行权利,等待唤醒
             }
        }
    }
}

就写到这里,下一篇写下互斥锁信号量这些。

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

随笔分类 -多线程编程

NET多线程探索-线程同步和通信 2012-03-20 16:53 by 海不是蓝, 426 visits,  网摘 ,  收藏 ,  编辑

 

0 Comment Categories:  多线程编程 Tags:  多线程 ,  同步 ,  线程通信

NET多线程探索-NET线程基础知识点 2012-03-19 13:40 by 海不是蓝, 132 visits,  网摘 ,  收藏 ,  编辑

 

0 Comment Categories:  .NET Framework ,  多线程编程 Tags:  多线程

作者: Leo_wl

    

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

    

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

版权信息

查看更多关于NET多线程探索线程同步和通信的详细内容...

  阅读:39次

上一篇: HTML5对联在线演示

下一篇:Yii 框架学习