c#之线程同步浅析(1)-----轻量级同步Interlocked

来源:互联网 发布:qq飞车帝王数据 编辑:程序博客网 时间:2024/05/17 08:30

转载自: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. }  

         

        希望对大家有用!在后面,我会继续讲解其他同步线程方法。



原创粉丝点击