c#之線程同步淺析(1)-----輕量級同步Interlocked

轉載自:http://blog.csdn.net/kkfdsa132/article/details/5474013

在c#裏面,實現線程同步方法有很多種。我瞭解到的至少有8種。這裏先講下,一種比較簡單的同步方法-------輕量級同步Interlock

        爲什麼說它是輕量級呢?因爲它僅對整形數據(即int類型,long也行)進行同步。如果你學過操作系統裏面的PV操作(即信號量),那麼你對它已經瞭解了一般。它實現的正是如同信號量的功能。下面是它提供的方法:

       

Interlocked.Increment(ref value) 數值加一(原子性操作)
Interlocked.Decrement(ref value) 數值減一(原子性操作)
Interlocked.Exchange(ref value1, value2) 交換:把值2賦給值1;返回新值
Interlocked.CompareExchange(ref value1, value2, value3) 實現比較和交換兩種功能:值1和值3比較,如果相同,把值2給值1,不相同則不作任何操作;返回原值(多用於判斷條件)(示例3中會用到)

 

      下面是它的幾個用法示例:

      (1)示例1:簡單輸出

 

[c-sharp] view plain copy
  1. int num = 0;  
  2. Interlocked.Increment(ref num);  
  3.          Console.WriteLine(num);  
  4.          Interlocked.Decrement(ref num);  
  5.          Console.WriteLine(num);  
  6.          Interlocked.Exchange(ref num, 10);  
  7.          Console.WriteLine(num);  
  8.          Console.WriteLine(Interlocked.CompareExchange(ref num, 100, 10));  
  9.          Console.WriteLine(num);  
          

             輸出結果如下:

            

 

      (2)示例2:創建1個後臺進程和50個前臺進程。後臺用於每隔一秒通報當期有幾個進程在運行。50個前臺進程則什麼事都不敢,只隨機停留1~12秒。代碼如下:

      

[c-sharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading;  
  6.    
  7. namespace InterlockExample  
  8. {  
  9.     class Program  
  10.     {  
  11.         //記錄線程數  
  12.         private static int threadCount = 0;  
  13.         private static Random rnd = new Random();  
  14.   
  15.         /// <summary>  
  16.         /// 創建一個線程,什麼事也不幹, 只隨機停留1~12秒  
  17.         /// </summary>  
  18.         private static void RndThreadFunc()  
  19.         {  
  20.             //新建線程,線程數加一  
  21.             Interlocked.Increment(ref threadCount);  
  22.   
  23.             try  
  24.             {     
  25.                 //什麼事也不幹, 只隨機停留1~12秒  
  26.                 int sleepTime = rnd.Next(1000, 12000);  
  27.                 Thread.Sleep(sleepTime);  
  28.             }  
  29.             finally  
  30.             {  
  31.                 //線程結束,線程數減一  
  32.                 Interlocked.Decrement(ref threadCount);  
  33.             }  
  34.         }  
  35.   
  36.         /// <summary>  
  37.         /// 每隔一秒,通報有幾個線程存在  
  38.         /// </summary>  
  39.         private static void RptThread()  
  40.         {   
  41.             while(true)  
  42.             {  
  43.                 int threadNumber = 0;  
  44.                 //獲取當前線程數  
  45.                 threadNumber = Interlocked.Exchange(ref threadCount, threadCount);  
  46.                 Console.WriteLine("{0} Thread(s) alive ", threadNumber);  
  47.                 //休眠1秒  
  48.                 Thread.Sleep(1000);  
  49.             }  
  50.         }  
  51.   
  52.         static void Main(string[] args)  
  53.         {  
  54.             //創建RptThread線程,每隔一秒通報有幾個線程存在  
  55.             Thread reporter = new Thread(new ThreadStart(RptThread));  
  56.             //設置爲後臺線程  
  57.             reporter.IsBackground = true;   
  58.             reporter.Start();  
  59.   
  60.             //創建50個RndThreadFunc線程  
  61.             Thread[] rndThreads = new Thread[50];  
  62.             for (int i = 0; i < 50; i++)  
  63.             {  
  64.                 rndThreads[i] = new Thread(new ThreadStart(RndThreadFunc));  
  65.                 rndThreads[i].Start();  
  66.             }  
  67.         }  
  68.     }  
  69. }  
  

      結果如下:(答案不唯一,因爲線程是隨機停留的)

      

 

       (3)示例3:對寫文件的加鎖操作。我們知道讀一個文件可以允許幾個人同時進行,而寫操作則每次只允許一人。這裏創建5個寫的進程,每次只能有一個進程可以寫,其他進程必須等待當前寫進程退出,纔可進入。代碼如下:

       

[c-sharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading; //線程  
  6. using System.IO;    //文件流  
  7.    
  8. namespace InterlockExample  
  9. {  
  10.     /// <summary>  
  11.     /// 一個類似於自旋鎖的類,也類似於對共享資源的訪問機制  
  12.     /// 如果資源已被佔有,則等待一段時間再嘗試訪問,如此循環,直到能夠獲得資源的使用權爲止  
  13.     /// </summary>  
  14.     public class SpinLock  
  15.     {  
  16.         //資源狀態鎖,0--未被佔有, 1--已被佔有  
  17.         private int theLock = 0;  
  18.         //等待時間  
  19.         private int spinWait;  
  20.   
  21.         public SpinLock(int spinWait)  
  22.         {  
  23.             this.spinWait = spinWait;  
  24.         }  
  25.   
  26.         /// <summary>  
  27.         /// 訪問  
  28.         /// </summary>  
  29.         public void Enter()  
  30.         {  
  31.             //如果已被佔有,則繼續等待  
  32.             while (Interlocked.CompareExchange(ref theLock, 1, 0) == 1)  
  33.             {  
  34.                 Thread.Sleep(spinWait);  
  35.             }  
  36.         }  
  37.   
  38.         /// <summary>  
  39.         /// 退出  
  40.         /// </summary>  
  41.         public void Exit()  
  42.         {  
  43.             //重置資源鎖  
  44.             Interlocked.Exchange(ref theLock, 0);  
  45.         }  
  46.     }  
  47.   
  48.     /// <summary>  
  49.     /// 自旋鎖的管理類   
  50.     /// </summary>  
  51.     public class SpinLockManager : IDisposable  //Disposable接口,實現一種非委託資源回收機制,可看作顯示回收資源。任務執行完畢後,會自動調用Dispose()裏面的方法。  
  52.     {  
  53.         private SpinLock spinLock;  
  54.   
  55.         public SpinLockManager(SpinLock spinLock)  
  56.         {  
  57.             this.spinLock = spinLock;  
  58.             spinLock.Enter();  
  59.         }  
  60.   
  61.         //任務結束後,執行Dispose()裏面的方法  
  62.         public void Dispose()  
  63.         {  
  64.             spinLock.Exit();  
  65.         }  
  66.     }  
  67.   
  68.     /// <summary>  
  69.     /// 主類  
  70.     /// </summary>  
  71.     class Program  
  72.     {  
  73.         private static Random rnd = new Random();  
  74.         //創建資源鎖,管理資源的訪問  
  75.         private static SpinLock logLock = new SpinLock(10);  
  76.         //以寫的方式打開文件,選擇追加模式  
  77.         private static StreamWriter fsLog =  
  78.             new StreamWriter(File.Open("Log.txt",  
  79.                 FileMode.Append,  
  80.                 FileAccess.Write,  
  81.                 FileShare.None));  
  82.   
  83.         /// <summary>  
  84.         /// 寫入文件  
  85.         /// </summary>  
  86.         private static void RndThreadFunc()  
  87.         {  
  88.             //創建SpinLockManager,並調用Dispose()方法。這裏採用using字段,是調用Dispose()方法的形式。  
  89.             using (new SpinLockManager(logLock))  
  90.             {  
  91.                 //寫入文件  
  92.                 fsLog.WriteLine("Thread Starting");  
  93.                 fsLog.Flush();  
  94.             }  
  95.   
  96.             int time = rnd.Next(10, 200);  
  97.             Thread.Sleep(time);  
  98.             //using語句,當在某個代碼段中使用了類的實例,只要離開了這個代碼段就自動調用這個類實例的Dispose。using只能用於實現了IDisposable接口的類型
  99.             using (new SpinLockManager(logLock))  
  100.             {  
  101.                 fsLog.WriteLine("Thread Exiting");  
  102.                 fsLog.Flush();  
  103.             }  
  104.         }  
  105.   
  106.         static void Main()  
  107.         {   
  108.             Thread[] rndThreads = new Thread[5];  
  109.   
  110.             //創建5個RndThreadFunc的線程  
  111.             for (int i = 0; i < 5; i++)  
  112.             {  
  113.                 rndThreads[i] = new Thread(new ThreadStart(RndThreadFunc));  
  114.                 rndThreads[i].Start();  
  115.             }  
  116.         }  
  117.     }  
  118. }  

         結果如下:

         

 

        (4)示例4:發牌程序。遊戲簡介:有一個發送線程負責發牌,4個接受線程(可以看做4個玩家)輪流接收牌。這裏實現的原理很像信號量的機制。具體說明:

        1、設有一個發送線程sender,依次產生1-52的數,就好像依次發出52張牌。

2、 同時有4個接收線程Receiver在依次接收Sender發出的52張牌

3、 每一輪中,發送線程Sender發送4張牌,4個接收線程輪流接收這四張牌。

4、 設置發送線程的優先級最高爲1,保證接收線程每執行一次,即接收一張牌後,再由發送線程執行一次。

          代碼如下(在後面的同步線程淺析中,會分別用到其他同步方法(lock,volatile,Monitor等 )實現這個範例,以此作爲講解。):附錄該實例下載地址:http://download.csdn.net/source/2230680

         

[c-sharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9. using System.Threading;  
  10.   
  11. namespace SendCard  
  12. {  
  13.   
  14.     public partial class SendCard : Form  
  15.     {  
  16.         private CardManager cardManager;  
  17.   
  18.         public SendCard()  
  19.         {  
  20.   
  21.             InitializeComponent();  
  22.             //該句是允許主線外其他線程操控Winform控件,因爲在Winform2.0以後,微軟加強了控件訪問的安全性,是不允許主線程外的其他線程訪問控件的。  
  23.             //如果不用這句會報錯。當然,你也可以採用別的方法屏蔽掉這個錯誤,例如委託。  
  24.             Control.CheckForIllegalCrossThreadCalls = false;  
  25.             cardManager = new CardManager(10);  
  26.         }  
  27.   
  28.         /// <summary>  
  29.         /// 發牌  
  30.         /// </summary>  
  31.         /// <param name="sender"></param>  
  32.         /// <param name="e"></param>  
  33.         private void btnSendCard_Click(object sender, EventArgs e)  
  34.         {  
  35.             //文本框清零  
  36.             foreach (Control cnt in this.Controls)  
  37.             {  
  38.                 if (cnt is RichTextBox)  
  39.                 {  
  40.                     ((RichTextBox)cnt).Clear();  
  41.                 }  
  42.             }  
  43.   
  44.             //創建發牌和接收牌類的實例  
  45.             Sender sender1 = new Sender(52, cardManager);   //發送52張牌  
  46.             Receiver[] receiver = new Receiver[4];  
  47.             //創建線程:1個發牌線程和4個接收牌的線程  
  48.             Thread[] receiveThread = new Thread[4];  
  49.             Thread sendThread = new Thread(new ThreadStart(sender1.SendCard));  
  50.             sendThread.Start();  
  51.   
  52.             //4個接收牌線程  
  53.             for (int i = 0; i < 4; i++)  
  54.             {  
  55.                 //依次獲取編輯框控件rtbPlayer0~rtbPlayer3  
  56.                 Control[] rtb = this.Controls.Find("rtbPlayer" + i.ToString(), true);  
  57.                 receiver[i] = new Receiver(i, cardManager, (RichTextBox)rtb[0], 13);  
  58.                 receiveThread[i] = new Thread(new ThreadStart(receiver[i].ReceiveCard));  
  59.                 receiveThread[i].Start();  
  60.             }  
  61.   
  62.         }  
  63.   
  64.     }  
  65.   
  66.     /// <summary>  
  67.     /// 管理髮牌和接受牌的類,類似於信號量的操作。主要有兩個信號:1.發牌信號--標記當期是否已發牌 2.接收牌玩家信號---標記輪到哪個玩家接受牌  
  68.     /// </summary>  
  69.     public class CardManager  
  70.     {  
  71.         //標記是否已發牌,0--下一張未發,1--已發  
  72.         private int hasCard = 0;  
  73.         //當前接收牌的玩家號:0~3  
  74.         private int order;  
  75.         //當前牌號  
  76.         private int value;  
  77.         //等待時間  
  78.         private int waitTime;  
  79.   
  80.         public CardManager(int time)  
  81.         {  
  82.             order = 0;  
  83.             this.waitTime = time;  
  84.         }  
  85.   
  86.         /// <summary>  
  87.         /// 發牌  
  88.         /// </summary>  
  89.         /// <param name="value">牌號</param>  
  90.         public void PutFunc(int value)  
  91.         {  
  92.            //已發牌則繼續等待,牌給取走,下一次發牌  
  93.             while (Interlocked.Exchange(ref hasCard, hasCard) == 1)  
  94.             {  
  95.                 Thread.Sleep(waitTime);  
  96.             }  
  97.             //新牌號  
  98.             this.value = value;  
  99.             //重置發牌狀態:已發  
  100.             Interlocked.Exchange(ref hasCard, 1);  
  101.         }  
  102.   
  103.         public int GetFunc(int order)  
  104.         {  
  105.             //等待接收牌,如果牌仍未發放或不是發給該玩家,則繼續等待  
  106.             while (Interlocked.Exchange(ref hasCard, hasCard) == 0 || Interlocked.Exchange(ref this.order, this.order) != order)  
  107.             {  
  108.                 Thread.Sleep(waitTime);  
  109.             }  
  110.             //更改爲下一個接收牌的玩家序號  
  111.             Interlocked.Exchange(ref this.order, (order+1)%4);  
  112.             //重置發牌狀態:未發  
  113.             Interlocked.Exchange(ref hasCard, 0);  
  114.   
  115.             return value;  
  116.         }  
  117.     }  
  118.   
  119.     /// <summary>  
  120.     /// 接收牌類  
  121.     /// </summary>  
  122.     public class Receiver  
  123.     {  
  124.         //玩家序號  
  125.         private int order;  
  126.         //牌管理對象  
  127.         private CardManager carManager;  
  128.         //文本編輯框  
  129.         private RichTextBox rtbPlay;  
  130.         //牌數量  
  131.         private int cardSum;  
  132.   
  133.         /// <summary>  
  134.         /// 接收牌--構造函數  
  135.         /// </summary>  
  136.         /// <param name="order">玩家序號</param>  
  137.         /// <param name="carManager">牌管理對象</param>  
  138.         /// <param name="rtbPlay">文本編輯框</param>  
  139.         /// <param name="cardSum">要接受的牌數量</param>  
  140.         public Receiver(int order, CardManager carManager, RichTextBox rtbPlay, int cardSum)  
  141.         {  
  142.             this.order = order;  
  143.             this.carManager = carManager;  
  144.             this.rtbPlay = rtbPlay;  
  145.             this.cardSum = cardSum;  
  146.         }  
  147.   
  148.         /// <summary>  
  149.         /// 接收牌  
  150.         /// </summary>  
  151.         public void ReceiveCard()  
  152.         {  
  153.             for (int i = 0; i < cardSum; i++)  
  154.             {  
  155.                 //添加到文本框  
  156.                 rtbPlay.AppendText(carManager.GetFunc(order) + " ");  
  157.                 Thread.Sleep(100);  
  158.             }  
  159.         }  
  160.     }  
  161.     
  162.     /// <summary>  
  163.     ///   發牌類  
  164.     /// </summary>  
  165.     public class Sender  
  166.     {  
  167.         //牌數量  
  168.         private int cardSum;  
  169.         //牌管理對象  
  170.         private CardManager cardManager;  
  171.   
  172.         public Sender(int cardSum, CardManager cardManager)  
  173.         {  
  174.             this.cardSum = cardSum;  
  175.             this.cardManager = cardManager;  
  176.         }  
  177.   
  178.         /// <summary>  
  179.         /// 發牌  
  180.         /// </summary>  
  181.         public void SendCard()  
  182.         {  
  183.             //標記該牌是否已發  
  184.             int[] card = new int[cardSum];  
  185.             Random rnd = new Random();  
  186.   
  187.             //全部初始化爲未發  
  188.             for (int i = 0; i < cardSum; i++)  
  189.             {  
  190.                 card[i] = 0;  
  191.             }  
  192.   
  193.             //發牌  
  194.             for (int i = 0; i < cardSum; i++)  
  195.             {  
  196.                 int k;  
  197.                 //隨機產生牌號,如果已發,則循環  
  198.                 do  
  199.                 {  
  200.                     k = rnd.Next(cardSum);  
  201.                 }while(card[k] == 1);  
  202.                 //發牌  
  203.                 cardManager.PutFunc(k + 1);  
  204.               //標記該牌已發過  
  205.                 card[k] = 1;  
  206.             }  
  207.         }  
  208.   
  209.     }  
  210.   
  211. }  

         結果如下:

        

         

          最後,要提醒一下的是:

          1.在。net2.0以後,微軟加強了控件的安全機制,不允許非主線程的線程非委託操控控件,如上面得例子代碼提到的。

             當然,有幾種方法解決這個問題:

              a.在構造函數裏添加一條語句:Control.CheckForIllegalCrossThreadCalls = false;

              b.採用委託機制,示例如下:

            

[c-sharp] view plain copy
  1. //1.聲明委託  
  2. private delegate void DlgShowThread();  
  3.   
  4. /// <summary>  
  5. /// 2.定義被調用方法  
  6. /// </summary>  
  7. public void ShowWord()  
  8. {  
  9.     textBox1.Text = "Welcom to c#";  
  10. }  
  11.   
  12. /// <summary>  
  13. ///3. 線程直接調用的方法  
  14. /// </summary>  
  15. public void ShowThread()  
  16. {  
  17.     //執行指定委託:Invoke()方法  
  18.     this.Invoke(new DlgShowThread(ShowWord));  
  19. }  
  20.   
  21. private void button1_Click(object sender, EventArgs e)  
  22. {  
  23.     //創建一個線程並開始  
  24.     Thread threadShow = new Thread(new ThreadStart(ShowThread));  
  25.     threadShow.Start();  
  26. }  

     

        2.關於控件的訪問:

          a.遍歷同種類型的控件,如上面代碼所示:

           

[c-sharp] view plain copy
  1. foreach (Control cnt in this.Controls)  
  2.           {  
  3.               if (cnt is RichTextBox)  
  4.               {  
  5.                   ((RichTextBox)cnt).Clear();  
  6.               }  
  7.           }  

            如果你的訪問的控件,僅是在一個名爲panel1的容器裏面,你可以修改如下:

            

[c-sharp] view plain copy
  1. foreach (Control cnt in this.panel1.Controls)  
  2.           {  
  3.               if (cnt is RichTextBox)  
  4.               {  
  5.                   ((RichTextBox)cnt).Clear();  
  6.               }  
  7.           }  
 

             b.通過字符串名轉爲control類型再具體轉爲該控件類型來訪問控件。常用於批處理多個名字前綴相同的控件,例如文本框textbox1,textbox2.。。,方法:this.Controls.Find(string controlName, bool boolValue)。這裏列出部分代碼:

              

[c-sharp] view plain copy
  1. Control[] rtb = this.Controls.Find("rtbPlayer" + i.ToString(), true);  
  2.          receiver[i] = new Receiver(i, cardManager, (RichTextBox)rtb[0], 13);  
 

 

         3.在示例3中出現的IDisposable接口,該接口主要用途是實現非託管資源的回收。在對象生命結束時,會自動調用Dispose裏面的方法。簡單的說,就是顯示回收資源。定義一個IDisposable類和調用它方法,請看下面示例:

       

[c-sharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace IDisposableExample  
  7. {  
  8.     class Program  
  9.     {  
  10.         /// <summary>  
  11.         /// 定義一個繼承IDisposable接口的類  
  12.         /// </summary>  
  13.         public class MyIDisposable : IDisposable  
  14.         {  
  15.             public MyIDisposable()  
  16.             {}  
  17.   
  18.             //輸出一串字符  
  19.             public void ShowMessage(string strText)  
  20.             {  
  21.                 Console.WriteLine(strText);  
  22.             }  
  23.   
  24.             //Dispose方法,在對象不再使用時,調用的方法。  
  25.             public void Dispose()  
  26.             {  
  27.                 ShowMessage("不再使用該對象,銷燬對象,回收資源。");  
  28.             }  
  29.   
  30.   
  31.         }  
  32.   
  33.         static void Main(string[] args)  
  34.         {  
  35.             //調用MyIDisposable類的兩種方法,兩種方法是等效的  
  36.             //方法一:採用using字段  
  37.             using (MyIDisposable myIDisposable1 = new MyIDisposable())  
  38.             {  
  39.                 myIDisposable1.ShowMessage("該對象正在工作中。。。。");  
  40.             }  
  41.   
  42.             Console.WriteLine();  
  43.   
  44.             //方法二:先創建該對象,再把該對象轉爲IDisposable類型,調用dispose()方法  
  45.             MyIDisposable myIDisposable2 = new MyIDisposable();  
  46.             try  
  47.             {  
  48.                 myIDisposable2.ShowMessage("該對象正在工作中。。。。");  
  49.             }  
  50.             finally  
  51.             {  
  52.                 IDisposable idisposable = myIDisposable2 as IDisposable;  
  53.                 if (idisposable != null)  
  54.                 {  
  55.                     idisposable.Dispose();  
  56.                 }  
  57.             }  
  58.         }  
  59.     }  
  60. }  

         

        希望對大家有用!在後面,我會繼續講解其他同步線程方法。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章