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

        在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:简单输出

 

int num = 0; Interlocked.Increment(ref num); Console.WriteLine(num); Interlocked.Decrement(ref num); Console.WriteLine(num); Interlocked.Exchange(ref num, 10); Console.WriteLine(num); Console.WriteLine(Interlocked.CompareExchange(ref num, 100, 10)); Console.WriteLine(num);           

             输出结果如下:

           

 

      (2)示例2:创建1个后台进程和50个前台进程。后台用于每隔一秒通报当期有几个进程在运行。50个前台进程则什么事都不敢,只随机停留1~12秒。代码如下:

      using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace InterlockExample { class Program { //记录线程数 private static int threadCount = 0; private static Random rnd = new Random(); ///

/// 创建一个线程,什么事也不干, 只随机停留1~12秒 /// private static void RndThreadFunc() { //新建线程,线程数加一 Interlocked.Increment(ref threadCount); try { //什么事也不干, 只随机停留1~12秒 int sleepTime = rnd.Next(1000, 12000); Thread.Sleep(sleepTime); } finally { //线程结束,线程数减一 Interlocked.Decrement(ref threadCount); } } /// /// 每隔一秒,通报有几个线程存在 /// private static void RptThread() { while(true) { int threadNumber = 0; //获取当前线程数 threadNumber = Interlocked.Exchange(ref threadCount, threadCount); Console.WriteLine("{0} Thread(s) alive ", threadNumber); //休眠1秒 Thread.Sleep(1000); } } static void Main(string[] args) { //创建RptThread线程,每隔一秒通报有几个线程存在 Thread reporter = new Thread(new ThreadStart(RptThread)); //设置为后台线程 reporter.IsBackground = true; reporter.Start(); //创建50个RndThreadFunc线程 Thread[] rndThreads = new Thread[50]; for (int i = 0; i < 50; i++) { rndThreads[i] = new Thread(new ThreadStart(RndThreadFunc)); rndThreads[i].Start(); } } } }   

      结果如下:(答案不唯一,因为线程是随机停留的)

     

 

       (3)示例3:对写文件的加锁操作。我们知道读一个文件可以允许几个人同时进行,而写操作则每次只允许一人。这里创建5个写的进程,每次只能有一个进程可以写,其他进程必须等待当前写进程退出,才可进入。代码如下:

        using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //线程 using System.IO; //文件流 namespace InterlockExample { ///

/// 一个类似于自旋锁的类,也类似于对共享资源的访问机制 /// 如果资源已被占有,则等待一段时间再尝试访问,如此循环,直到能够获得资源的使用权为止 /// public class SpinLock { //资源状态锁,0--未被占有, 1--已被占有 private int theLock = 0; //等待时间 private int spinWait; public SpinLock(int spinWait) { this.spinWait = spinWait; } /// /// 访问 /// public void Enter() { //如果已被占有,则继续等待 while (Interlocked.CompareExchange(ref theLock, 1, 0) == 1) { Thread.Sleep(spinWait); } } /// /// 退出 /// public void Exit() { //重置资源锁 Interlocked.Exchange(ref theLock, 0); } } /// /// 自旋锁的管理类 /// public class SpinLockManager : IDisposable //Disposable接口,实现一种非委托资源回收机制,可看作显示回收资源。任务执行完毕后,会自动调用Dispose()里面的方法。 { private SpinLock spinLock; public SpinLockManager(SpinLock spinLock) { this.spinLock = spinLock; spinLock.Enter(); } //任务结束后,执行Dispose()里面的方法 public void Dispose() { spinLock.Exit(); } } /// /// 主类 /// class Program { private static Random rnd = new Random(); //创建资源锁,管理资源的访问 private static SpinLock logLock = new SpinLock(10); //以写的方式打开文件,选择追加模式 private static StreamWriter fsLog = new StreamWriter(File.Open("Log.txt", FileMode.Append, FileAccess.Write, FileShare.None)); /// /// 写入文件 /// private static void RndThreadFunc() { //创建SpinLockManager,并调用Dispose()方法。这里采用using字段,是调用Dispose()方法的形式。 using (new SpinLockManager(logLock)) { //写入文件 fsLog.WriteLine("Thread Starting"); fsLog.Flush(); } int time = rnd.Next(10, 200); Thread.Sleep(time); using (new SpinLockManager(logLock)) { fsLog.WriteLine("Thread Exiting"); fsLog.Flush(); } } static void Main() { Thread[] rndThreads = new Thread[5]; //创建5个RndThreadFunc的线程 for (int i = 0; i < 5; i++) { rndThreads[i] = new Thread(new ThreadStart(RndThreadFunc)); rndThreads[i].Start(); } } } }

         结果如下:

        

 

        (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

         using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace SendCard { public partial class SendCard : Form { private CardManager cardManager; public SendCard() { InitializeComponent(); //该句是允许主线外其他线程操控Winform控件,因为在Winform2.0以后,微软加强了控件访问的安全性,是不允许主线程外的其他线程访问控件的。 //如果不用这句会报错。当然,你也可以采用别的方法屏蔽掉这个错误,例如委托。 Control.CheckForIllegalCrossThreadCalls = false; cardManager = new CardManager(10); } ///

/// 发牌 /// /// /// private void btnSendCard_Click(object sender, EventArgs e) { //文本框清零 foreach (Control cnt in this.Controls) { if (cnt is RichTextBox) { ((RichTextBox)cnt).Clear(); } } //创建发牌和接收牌类的实例 Sender sender1 = new Sender(52, cardManager); //发送52张牌 Receiver[] receiver = new Receiver[4]; //创建线程:1个发牌线程和4个接收牌的线程 Thread[] receiveThread = new Thread[4]; Thread sendThread = new Thread(new ThreadStart(sender1.SendCard)); sendThread.Start(); //4个接收牌线程 for (int i = 0; i < 4; i++) { //依次获取编辑框控件rtbPlayer0~rtbPlayer3 Control[] rtb = this.Controls.Find("rtbPlayer" + i.ToString(), true); receiver[i] = new Receiver(i, cardManager, (RichTextBox)rtb[0], 13); receiveThread[i] = new Thread(new ThreadStart(receiver[i].ReceiveCard)); receiveThread[i].Start(); } } } /// /// 管理发牌和接受牌的类,类似于信号量的操作。主要有两个信号:1.发牌信号--标记当期是否已发牌 2.接收牌玩家信号---标记轮到哪个玩家接受牌 /// public class CardManager { //标记是否已发牌,0--下一张未发,1--已发 private int hasCard = 0; //当前接收牌的玩家号:0~3 private int order; //当前牌号 private int value; //等待时间 private int waitTime; public CardManager(int time) { order = 0; this.waitTime = time; } /// /// 发牌 /// /// 牌号 public void PutFunc(int value) { //已发牌则继续等待,牌给取走,下一次发牌 while (Interlocked.Exchange(ref hasCard, hasCard) == 1) { Thread.Sleep(waitTime); } //新牌号 this.value = value; //重置发牌状态:已发 Interlocked.Exchange(ref hasCard, 1); } public int GetFunc(int order) { //等待接收牌,如果牌仍未发放或不是发给该玩家,则继续等待 while (Interlocked.Exchange(ref hasCard, hasCard) == 0 || Interlocked.Exchange(ref this.order, this.order) != order) { Thread.Sleep(waitTime); } //更改为下一个接收牌的玩家序号 Interlocked.Exchange(ref this.order, (order+1)%4); //重置发牌状态:未发 Interlocked.Exchange(ref hasCard, 0); return value; } } /// /// 接收牌类 /// public class Receiver { //玩家序号 private int order; //牌管理对象 private CardManager carManager; //文本编辑框 private RichTextBox rtbPlay; //牌数量 private int cardSum; /// /// 接收牌--构造函数 /// /// 玩家序号 /// 牌管理对象 /// 文本编辑框 /// 要接受的牌数量 public Receiver(int order, CardManager carManager, RichTextBox rtbPlay, int cardSum) { this.order = order; this.carManager = carManager; this.rtbPlay = rtbPlay; this.cardSum = cardSum; } /// /// 接收牌 /// public void ReceiveCard() { for (int i = 0; i < cardSum; i++) { //添加到文本框 rtbPlay.AppendText(carManager.GetFunc(order) + " "); Thread.Sleep(100); } } } /// /// 发牌类 /// public class Sender { //牌数量 private int cardSum; //牌管理对象 private CardManager cardManager; public Sender(int cardSum, CardManager cardManager) { this.cardSum = cardSum; this.cardManager = cardManager; } /// /// 发牌 /// public void SendCard() { //标记该牌是否已发 int[] card = new int[cardSum]; Random rnd = new Random(); //全部初始化为未发 for (int i = 0; i < cardSum; i++) { card[i] = 0; } //发牌 for (int i = 0; i < cardSum; i++) { int k; //随机产生牌号,如果已发,则循环 do { k = rnd.Next(cardSum); }while(card[k] == 1); //发牌 cardManager.PutFunc(k + 1); //标记该牌已发过 card[k] = 1; } } } }

         结果如下:

       

         

          最后,要提醒一下的是:

          1.在。net2.0以后,微软加强了控件的安全机制,不允许非主线程的线程非委托操控控件,如上面得例子代码提到的。

             当然,有几种方法解决这个问题:

              a.在构造函数里添加一条语句:Control.CheckForIllegalCrossThreadCalls = false;

              b.采用委托机制,示例如下:

             //1.声明委托 private delegate void DlgShowThread(); ///

/// 2.定义被调用方法 /// public void ShowWord() { textBox1.Text = "Welcom to c#"; } /// ///3. 线程直接调用的方法 /// public void ShowThread() { //执行指定委托:Invoke()方法 this.Invoke(new DlgShowThread(ShowWord)); } private void button1_Click(object sender, EventArgs e) { //创建一个线程并开始 Thread threadShow = new Thread(new ThreadStart(ShowThread)); threadShow.Start(); }

     

        2.关于控件的访问:

          a.遍历同种类型的控件,如上面代码所示:

            foreach (Control cnt in this.Controls) { if (cnt is RichTextBox) { ((RichTextBox)cnt).Clear(); } }

            如果你的访问的控件,仅是在一个名为panel1的容器里面,你可以修改如下:

             foreach (Control cnt in this.panel1.Controls) { if (cnt is RichTextBox) { ((RichTextBox)cnt).Clear(); } } 

             b.通过字符串名转为control类型再具体转为该控件类型来访问控件。常用于批处理多个名字前缀相同的控件,例如文本框textbox1,textbox2.。。,方法:this.Controls.Find(string controlName, bool boolValue)。这里列出部分代码:

               Control[] rtb = this.Controls.Find("rtbPlayer" + i.ToString(), true); receiver[i] = new Receiver(i, cardManager, (RichTextBox)rtb[0], 13); 

 

         3.在示例3中出现的IDisposable接口,该接口主要用途是实现非托管资源的回收。在对象生命结束时,会自动调用Dispose里面的方法。简单的说,就是显示回收资源。定义一个IDisposable类和调用它方法,请看下面示例:

        using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IDisposableExample { class Program { ///

/// 定义一个继承IDisposable接口的类 /// public class MyIDisposable : IDisposable { public MyIDisposable() {} //输出一串字符 public void ShowMessage(string strText) { Console.WriteLine(strText); } //Dispose方法,在对象不再使用时,调用的方法。 public void Dispose() { ShowMessage("不再使用该对象,销毁对象,回收资源。"); } } static void Main(string[] args) { //调用MyIDisposable类的两种方法,两种方法是等效的 //方法一:采用using字段 using (MyIDisposable myIDisposable1 = new MyIDisposable()) { myIDisposable1.ShowMessage("该对象正在工作中。。。。"); } Console.WriteLine(); //方法二:先创建该对象,再把该对象转为IDisposable类型,调用dispose()方法 MyIDisposable myIDisposable2 = new MyIDisposable(); try { myIDisposable2.ShowMessage("该对象正在工作中。。。。"); } finally { IDisposable idisposable = myIDisposable2 as IDisposable; if (idisposable != null) { idisposable.Dispose(); } } } } }

         

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

你可能感兴趣的:(C#篇)