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 }

