NET多线程探索-线程同步和通信
2012-03-20 16:53 海不是蓝 阅读(2309) 评论(8) 编辑 收藏 举报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(Minute); T2.Start(Minute); } public void DI(bool run) { lock (obj) { Console.WriteLine("嘀"); Thread.Sleep(1000); Monitor.Pulse(obj);//执行完毕,唤醒其它线程 Monitor.Wait(obj);//进入暂停,移交执行权利,等待唤醒 } } public void DA(bool run) { lock (obj) { Console.WriteLine("嗒"); Thread.Sleep(1000); Monitor.Pulse(obj);//执行完毕,唤醒其它线程 Monitor.Wait(obj);//进入暂停,移交执行权利,等待唤醒 } } }
谢谢Shikyoh指出错误,已经修改
就写到这里,下一篇写下互斥锁信号量这些。