NET多线程探索线程同步和通信
NET多线程探索-线程同步和通信
2012-03-20 16:53 by 海不是蓝, 426 visits, 收藏 , 编辑
NET中各种线程同步方法
在NET多线程开发中,有时候需要多个线程协调工作,完成这个步骤的过程称为“同步”。
使用同步的主要原因:
1.多个线程访问同一个共享资源。
2.多线程写入文件时保证只有一个线程使用文件资源。 3.由事件引发线程,线程等待事件,需要挂起线程。
NET中线程同步常见的几种方法:
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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息