1.最簡單的多線程調用
System.Threading.Thread類構造方法接受一個ThreadStart委托,改委托不帶參數,無返回值
1 public static void Start1() 2 { 3 Console.WriteLine("this is main thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 4 System.Threading.ThreadStart start = Method1; 5 Thread thread = new Thread(start); 6 thread.IsBackground = true; 7 thread.Start(); 8 Console.WriteLine("main thread other thing..."); 9 } 10 public static void Method1() 11 { 12 Console.WriteLine("this is sub thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 13 Thread.Sleep(TimeSpan.FromSeconds(3)); 14 Console.WriteLine("sub thread other thing..."); 15 }
注意thread.IsBackground=true,利用Thread創建的線程默認是前台線程,即IsBackground=false,而線程池中的線程是后台線程。
前台線程和后台線程的區別在於:當主線程執行結束時,若任然有前台線程在執行,則應用程序的進程任然處於激活狀態,直到前台線程執行完畢;而換成后台線程,當主線程結束時,后台線程也跟着結束了。
2.給線程傳送數據
這是使用ParameterizedThreadStart 委托來代替ThreadStart委托,ParameterizedThreadStart 委托接受一個帶object的參數,無返回值
1 public static void Start2() 2 { 3 Customer c = new Customer { ID = "aaa", Name = "name" }; 4 Console.WriteLine("this is main thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 5 ParameterizedThreadStart start = Method2; 6 Thread thread = new Thread(start); 7 thread.Start(c); 8 Console.WriteLine("main thread other thing..."); 9 } 10 public static void Method2(object o) 11 { 12 Console.WriteLine("this is sub thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 13 Console.WriteLine(o.ToString()); 14 Thread.Sleep(TimeSpan.FromSeconds(3)); 15 Console.WriteLine("sub thread other thing..."); 16 }
由此實例可以看出,我們將一個Customer 實例傳入了新線程中,新線程可以直接讀取此參數的信息。
當然還有另一種方法也可以將數據傳入線程中,創建一個類,把線程的方法定義為實例方法,這樣就可以初始化實例的數據,之后啟動線程,還是看實例代碼:
1 public static void Start4() 2 { 3 Customer c = new Customer(); 4 //調用同一個對象,從而實現資源共享 5 ThreadStart ts = c.Increase; 6 Thread[] tArray = new Thread[20]; 7 for (int i = 0; i < 20; i++) 8 { 9 tArray[i] = new Thread(ts); 10 tArray[i].Start(); 11 } 12 for (int i = 0; i < 20; i++) 13 { 14 tArray[i].Join(); 15 } 16 Console.WriteLine(c.Number.ToString()); 17 } 18 public static void Method3(object o) 19 { 20 Customer c = o as Customer; 21 //若不上鎖,所以每次結果都不同 22 //應該重新建立一個object進行上鎖,因為外邊還有可能訪問到c這個實例 23 lock (c) 24 { 25 for (int j = 0; j < 1000; j++) 26 { 27 c.Number++; 28 } 29 } 30 }
Customer類的定義如下:
1 public class Customer 2 { 3 public int Number 4 { 5 get; 6 set; 7 } 8 public string ID 9 { 10 get; 11 set; 12 } 13 14 public string Name 15 { 16 get; 17 set; 18 } 19 public Customer() 20 { 21 Number = 0; 22 } 23 public void Increase() 24 { 25 object o = new object(); 26 lock (o) 27 { 28 for (int i = 0; i < 1000; i++) 29 { 30 Number++; 31 } 32 } 33 } 34 }
3.競態條件
來看競態條件的定義: 如果兩個或多個線程訪問相同的對象,或者訪問不同步的共享狀態,就會出現競態條件。
競態條件也是多線程編程的常犯的錯誤,如果代碼不夠健壯,多線程編碼會出現一些預想不到的結果,我們來根據一個實例來看:
1 public static void RaceCondition() 2 { 3 ThreadStart method = ChangeState; 4 //這里放出20個線程 5 for (int i = 0; i < 20; i++) 6 { 7 Thread t = new Thread(method); 8 t.Name = i.ToString() + "aa"; 9 t.Start(); 10 } 11 } 12 //2.線程調用的方法,改變狀態值 13 public static void ChangeState() 14 { 15 for (int loop = 0; loop < 1000; loop++) 16 { 17 int state = 5; 18 if (state == 5) 19 { 20 //此處第一個線程進入后沒來得及++操作,第二個線程又進入,此時第一個線程做了++操作,第二個 21 //線程繼續++,state的值變成7 22 state++; 23 if (state == 7) 24 { 25 //沒有試驗成功 26 Console.WriteLine("state={0},loop={1}", state, loop); 27 Console.WriteLine("thread name:{0}", Thread.CurrentThread.Name); 28 } 29 //Console.WriteLine(state.ToString()); 30 } 31 } 32 }
最簡單的解決競態條件的辦法就是使用上鎖-lock,鎖定共享的對象。用lock語句鎖定在線程中共享的變量state,只有一個線程能在鎖定塊中處理共享的state對象。由於這個對象由所有的線程共享,因此如果一個線程鎖定了state,另一個線程就必須等待該鎖定的解除。
1 public static void ChangeState2() 2 { 3 object o = new object(); 4 for (int loop = 0; loop < 100; loop++) 5 { 6 int state = 5; 7 lock (o) 8 { 9 if (state == 5) 10 { 11 state++; 12 if (state == 7) 13 { 14 //沒有試驗成功 15 Console.WriteLine("state={0},loop={1}", state, loop); 16 Console.WriteLine("thread name:{0}", Thread.CurrentThread.Name); 17 } 18 } 19 } 20 } 21 }
4.死鎖
在死鎖中,至少有兩個線程被掛起,等待對方解除鎖定。由於兩個線程都在等待對方,就出現了死鎖,線程將無限等待下去。
5.幾種同步方法
上面介紹了兩種線程數據共享的辦法,一旦需要共享數據,就必須使用同步技術,確保一次只有一個線程訪問和改變共享狀態。上面介紹了使用lock的方法防止競態條件的發生,但是如果用不好的話會產生死鎖。那么下面再介紹幾種針對不同情況使用的線程同步方法。
(1)SyncRoot模式
下面創建一個類的兩個版本,一個同步版本,一個異步版本
1 public class GeneralDemo 2 { 3 public virtual bool IsSynchronized 4 { 5 get { return false; } 6 } 7 public static GeneralDemo Synchronized(GeneralDemo demo) 8 { 9 if (demo.IsSynchronized) 10 { 11 return new SyncDemo(demo); 12 } 13 return demo; 14 } 15 public virtual void DoThis() 16 { } 17 public virtual void DoThat() 18 { } 19 }
1 //同步版本 2 private class SyncDemo : GeneralDemo 3 { 4 private object syncRoot = new object(); 5 private GeneralDemo demo; 6 private int state = 0; 7 8 public int State 9 { 10 get { return state; } 11 set { state = value; } 12 } 13 public SyncDemo(GeneralDemo demo) 14 { 15 this.demo = demo; 16 } 17 public override bool IsSynchronized 18 { 19 get 20 { 21 return true; 22 } 23 } 24 public override void DoThat() 25 { 26 lock (syncRoot) 27 { 28 demo.DoThis(); 29 } 30 } 31 public override void DoThis() 32 { 33 lock (syncRoot) 34 { 35 demo.DoThis(); 36 } 37 }
需要注意的是在SyncDemo類中,只有方法是同步的,對於這個類的成員調用並沒有同步,如果試圖用SyncRoot模式鎖定對屬性的訪問,對state的訪問變成線程安全的,仍會出現競態條件
即這樣做是不可取的:
1 //public int State 2 //{ 3 // get { lock (syncRoot) { return state; } } 4 // set { lock (syncRoot) { state = value; } } 5 //}
1 public int State 2 { 3 get 4 { 5 return Interlocked.Increment(ref state); 6 } 7 }
(3)Monitor類
1 public override void DoThis() 2 { 3 if (Monitor.TryEnter(syncRoot, 500)) 4 { 5 try 6 { 7 //acquired the lock 8 //synchroized region for syncRoot 9 } 10 finally 11 { 12 Monitor.Exit(syncRoot); 13 } 14 } 15 else 16 { 17 //didn't get the lock,do something else 18 } 19 }