NET多線程探索-線程同步和通信
2012-03-20 16:53 海不是藍 閱讀(2330) 評論(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指出錯誤,已經修改
就寫到這里,下一篇寫下互斥鎖信號量這些。








浙公網安備 33010602011771號