C#中的線程四(System.Threading.Thread)


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   //}
最好的辦法是把lock添加到調用State的地方,當然鎖定狀態遞增還有一種更快的方式
(2)Interlocked
復制代碼
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 }
復制代碼

 

作者: Richie Zhang
聲明:本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM