轉載自: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:簡單輸出
- 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();
- /// <summary>
- /// 創建一個線程,什麼事也不幹, 只隨機停留1~12秒
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// 每隔一秒,通報有幾個線程存在
- /// </summary>
- 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
- {
- /// <summary>
- /// 一個類似於自旋鎖的類,也類似於對共享資源的訪問機制
- /// 如果資源已被佔有,則等待一段時間再嘗試訪問,如此循環,直到能夠獲得資源的使用權爲止
- /// </summary>
- public class SpinLock
- {
- //資源狀態鎖,0--未被佔有, 1--已被佔有
- private int theLock = 0;
- //等待時間
- private int spinWait;
- public SpinLock(int spinWait)
- {
- this.spinWait = spinWait;
- }
- /// <summary>
- /// 訪問
- /// </summary>
- public void Enter()
- {
- //如果已被佔有,則繼續等待
- while (Interlocked.CompareExchange(ref theLock, 1, 0) == 1)
- {
- Thread.Sleep(spinWait);
- }
- }
- /// <summary>
- /// 退出
- /// </summary>
- public void Exit()
- {
- //重置資源鎖
- Interlocked.Exchange(ref theLock, 0);
- }
- }
- /// <summary>
- /// 自旋鎖的管理類
- /// </summary>
- public class SpinLockManager : IDisposable //Disposable接口,實現一種非委託資源回收機制,可看作顯示回收資源。任務執行完畢後,會自動調用Dispose()裏面的方法。
- {
- private SpinLock spinLock;
- public SpinLockManager(SpinLock spinLock)
- {
- this.spinLock = spinLock;
- spinLock.Enter();
- }
- //任務結束後,執行Dispose()裏面的方法
- public void Dispose()
- {
- spinLock.Exit();
- }
- }
- /// <summary>
- /// 主類
- /// </summary>
- 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));
- /// <summary>
- /// 寫入文件
- /// </summary>
- 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語句,當在某個代碼段中使用了類的實例,只要離開了這個代碼段就自動調用這個類實例的Dispose。using只能用於實現了IDisposable接口的類型
- 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);
- }
- /// <summary>
- /// 發牌
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- 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();
- }
- }
- }
- /// <summary>
- /// 管理髮牌和接受牌的類,類似於信號量的操作。主要有兩個信號:1.發牌信號--標記當期是否已發牌 2.接收牌玩家信號---標記輪到哪個玩家接受牌
- /// </summary>
- 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;
- }
- /// <summary>
- /// 發牌
- /// </summary>
- /// <param name="value">牌號</param>
- 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;
- }
- }
- /// <summary>
- /// 接收牌類
- /// </summary>
- public class Receiver
- {
- //玩家序號
- private int order;
- //牌管理對象
- private CardManager carManager;
- //文本編輯框
- private RichTextBox rtbPlay;
- //牌數量
- private int cardSum;
- /// <summary>
- /// 接收牌--構造函數
- /// </summary>
- /// <param name="order">玩家序號</param>
- /// <param name="carManager">牌管理對象</param>
- /// <param name="rtbPlay">文本編輯框</param>
- /// <param name="cardSum">要接受的牌數量</param>
- public Receiver(int order, CardManager carManager, RichTextBox rtbPlay, int cardSum)
- {
- this.order = order;
- this.carManager = carManager;
- this.rtbPlay = rtbPlay;
- this.cardSum = cardSum;
- }
- /// <summary>
- /// 接收牌
- /// </summary>
- public void ReceiveCard()
- {
- for (int i = 0; i < cardSum; i++)
- {
- //添加到文本框
- rtbPlay.AppendText(carManager.GetFunc(order) + " ");
- Thread.Sleep(100);
- }
- }
- }
- /// <summary>
- /// 發牌類
- /// </summary>
- public class Sender
- {
- //牌數量
- private int cardSum;
- //牌管理對象
- private CardManager cardManager;
- public Sender(int cardSum, CardManager cardManager)
- {
- this.cardSum = cardSum;
- this.cardManager = cardManager;
- }
- /// <summary>
- /// 發牌
- /// </summary>
- 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();
- /// <summary>
- /// 2.定義被調用方法
- /// </summary>
- public void ShowWord()
- {
- textBox1.Text = "Welcom to c#";
- }
- /// <summary>
- ///3. 線程直接調用的方法
- /// </summary>
- 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
- {
- /// <summary>
- /// 定義一個繼承IDisposable接口的類
- /// </summary>
- 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();
- }
- }
- }
- }
- }
希望對大家有用!在後面,我會繼續講解其他同步線程方法。