C#多線(xiàn)程及同步示例簡(jiǎn)析
60年代,在OS中能擁有資源和獨(dú)立運(yùn)行的基本單位是進(jìn)程,然而隨著計(jì)算機(jī)技術(shù)的發(fā)展,進(jìn)程出現(xiàn)了很多弊端,一是由于進(jìn)程是資源擁有者,創(chuàng)建、撤消與切換存在較大的時(shí)空開(kāi)銷(xiāo),因此需要引入輕型進(jìn)程;二是由于對(duì)稱(chēng)多處理機(jī)(SMP)出現(xiàn),可以滿(mǎn)足多個(gè)運(yùn)行單位,而多個(gè)進(jìn)程并行開(kāi)銷(xiāo)過(guò)大。
因此在80年代,出現(xiàn)了能獨(dú)立運(yùn)行的基本單位——線(xiàn)程(Threads)。
線(xiàn)程,有時(shí)被稱(chēng)為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個(gè)標(biāo)準(zhǔn)的線(xiàn)程由線(xiàn)程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。另外,線(xiàn)程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線(xiàn)程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線(xiàn)程共享進(jìn)程所擁有的全部資源。一個(gè)線(xiàn)程可以創(chuàng)建和撤消另一個(gè)線(xiàn)程,同一進(jìn)程中的多個(gè)線(xiàn)程之間可以并發(fā)執(zhí)行。由于線(xiàn)程之間的相互制約,致使線(xiàn)程在運(yùn)行中呈現(xiàn)出間斷性。線(xiàn)程也有就緒、阻塞和運(yùn)行三種基本狀態(tài)。就緒狀態(tài)是指線(xiàn)程具備運(yùn)行的所有條件,邏輯上可以運(yùn)行,在等待處理機(jī);運(yùn)行狀態(tài)是指線(xiàn)程占有處理機(jī)正在運(yùn)行;阻塞狀態(tài)是指線(xiàn)程在等待一個(gè)事件(如某個(gè)信號(hào)量),邏輯上不可執(zhí)行。每一個(gè)程序都至少有一個(gè)線(xiàn)程,若程序只有一個(gè)線(xiàn)程,那就是程序本身。
線(xiàn)程是程序中一個(gè)單一的順序控制流程。進(jìn)程內(nèi)一個(gè)相對(duì)獨(dú)立的、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位指運(yùn)行中的程序的調(diào)度單位。在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線(xiàn)程完成不同的工作,稱(chēng)為多線(xiàn)程。
一、線(xiàn)程簡(jiǎn)義
1、進(jìn)程與線(xiàn)程:進(jìn)程作為操作系統(tǒng)執(zhí)行程序的基本單位,擁有應(yīng)用程序的資源,進(jìn)程包含線(xiàn)程,進(jìn)程的資源被線(xiàn)程共享,線(xiàn)程不擁有資源。
2、前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程:通過(guò)Thread類(lèi)新建線(xiàn)程默認(rèn)為前臺(tái)線(xiàn)程。當(dāng)所有前臺(tái)線(xiàn)程關(guān)閉時(shí),所有的后臺(tái)線(xiàn)程也會(huì)被直接終止,不會(huì)拋出異常。
3、掛起(Suspend)和喚醒(Resume):由于線(xiàn)程的執(zhí)行順序和程序的執(zhí)行情況不可預(yù)知,所以使用掛起和喚醒容易發(fā)生死鎖的情況,在實(shí)際應(yīng)用中應(yīng)該盡量少用。
4、阻塞線(xiàn)程:Join,阻塞調(diào)用線(xiàn)程,直到該線(xiàn)程終止。
5、終止線(xiàn)程:Abort:拋出 ThreadAbortException 異常讓線(xiàn)程終止,終止后的線(xiàn)程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線(xiàn)程終止,通過(guò)捕獲異??梢岳^續(xù)執(zhí)行。
6、線(xiàn)程優(yōu)先級(jí):AboveNormal BelowNormal Highest Lowest Normal,默認(rèn)為Normal。
二、線(xiàn)程的使用
線(xiàn)程函數(shù)通過(guò)委托傳遞,可以不帶參數(shù),也可以帶參數(shù)(只能有一個(gè)參數(shù)),可以用一個(gè)類(lèi)或結(jié)構(gòu)體封裝參數(shù)。
namespace Test { class Program { static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(TestMethod)); Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod)); t1.IsBackground = true; t2.IsBackground = true; t1.Start(); t2.Start("hello"); Console.ReadKey(); } public static void TestMethod() { Console.WriteLine("不帶參數(shù)的線(xiàn)程函數(shù)"); } public static void TestMethod(object data) { string datastr = data as string; Console.WriteLine("帶參數(shù)的線(xiàn)程函數(shù),參數(shù)為:{0}", datastr); } } }
三、線(xiàn)程池
由于線(xiàn)程的創(chuàng)建和銷(xiāo)毀需要耗費(fèi)一定的開(kāi)銷(xiāo),過(guò)多的使用線(xiàn)程會(huì)造成內(nèi)存資源的浪費(fèi),出于對(duì)性能的考慮,于是引入了線(xiàn)程池的概念。線(xiàn)程池維護(hù)一個(gè)請(qǐng)求隊(duì)列,線(xiàn)程池的代碼從隊(duì)列提取任務(wù),然后委派給線(xiàn)程池的一個(gè)線(xiàn)程執(zhí)行,線(xiàn)程執(zhí)行完不會(huì)被立即銷(xiāo)毀,這樣既可以在后臺(tái)執(zhí)行任務(wù),又可以減少線(xiàn)程創(chuàng)建和銷(xiāo)毀所帶來(lái)的開(kāi)銷(xiāo)。
線(xiàn)程池線(xiàn)程默認(rèn)為后臺(tái)線(xiàn)程(IsBackground)。
class Program { static void Main(string[] args) { //將工作項(xiàng)加入到線(xiàn)程池隊(duì)列中,這里可以傳遞一個(gè)線(xiàn)程參數(shù) ThreadPool.QueueUserWorkItem(TestMethod, "Hello"); Console.ReadKey(); } public static void TestMethod(object data) { string datastr = data as string; Console.WriteLine(datastr); } }
四、Task類(lèi)
使用ThreadPool的QueueUserWorkItem()方法發(fā)起一次異步的線(xiàn)程執(zhí)行很簡(jiǎn)單,但是該方法最大的問(wèn)題是沒(méi)有一個(gè)內(nèi)建的機(jī)制讓你知道操作什么時(shí)候完成,有沒(méi)有一個(gè)內(nèi)建的機(jī)制在操作完成后獲得一個(gè)返回值。為此,可以使用System.Threading.Tasks中的Task類(lèi)。
構(gòu)造一個(gè)Task<TResult>對(duì)象,并為泛型TResult參數(shù)傳遞一個(gè)操作的返回類(lèi)型。
class Program { static void Main(string[] args) { Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000); t.Start(); t.Wait(); Console.WriteLine(t.Result); Console.ReadKey(); } private static Int32 Sum(Int32 n) { Int32 sum = 0; for (; n > 0; --n) checked{ sum += n;} //結(jié)果太大,拋出異常 return sum; } }
一個(gè)任務(wù)完成時(shí),自動(dòng)啟動(dòng)一個(gè)新任務(wù)。
一個(gè)任務(wù)完成后,它可以啟動(dòng)另一個(gè)任務(wù),下面重寫(xiě)了前面的代碼,不阻塞任何線(xiàn)程。
class Program { static void Main(string[] args) { Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000); t.Start(); //t.Wait(); Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result)); Console.ReadKey(); } private static Int32 Sum(Int32 n) { Int32 sum = 0; for (; n > 0; --n) checked{ sum += n;} //結(jié)果溢出,拋出異常 return sum; } }
五、委托異步執(zhí)行
委托的異步調(diào)用:BeginInvoke() 和 EndInvoke()
public delegate string MyDelegate(object data); class Program { static void Main(string[] args) { MyDelegate mydelegate = new MyDelegate(TestMethod); IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param"); //異步執(zhí)行完成 string resultstr = mydelegate.EndInvoke(result); } //線(xiàn)程函數(shù) public static string TestMethod(object data) { string datastr = data as string; return datastr; } //異步回調(diào)函數(shù) public static void TestCallback(IAsyncResult data) { Console.WriteLine(data.AsyncState); } }
六、線(xiàn)程同步
1)原子操作(Interlocked):幫助保護(hù)免受計(jì)劃程序切換上下文時(shí)某個(gè)線(xiàn)程正在更新可以由其他線(xiàn)程訪(fǎng)問(wèn)的變量或者在單獨(dú)的處理器上同時(shí)執(zhí)行兩個(gè)線(xiàn)程就可能出現(xiàn)的錯(cuò)誤。 此類(lèi)的成員不會(huì)引發(fā)異常。
class Program { static int counter = 1; static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(F1)); Thread t2 = new Thread(new ThreadStart(F2)); t1.Start(); t2.Start(); t1.Join(); t2.Join(); System.Console.ReadKey(); } static void F1() { for (int i = 0; i < 5; i++) { Interlocked.Increment(ref counter); System.Console.WriteLine("Counter++ {0}", counter); Thread.Sleep(10); } } static void F2() { for (int i = 0; i < 5; i++) { Interlocked.Decrement(ref counter); System.Console.WriteLine("Counter-- {0}", counter); Thread.Sleep(10); } } }
2)lock()語(yǔ)句:避免鎖定public類(lèi)型,否則實(shí)例將超出代碼控制的范圍,定義private對(duì)象來(lái)鎖定。而自定義類(lèi)推薦用私有的只讀靜態(tài)對(duì)象,比如:private static readonly object obj = new object();為什么要設(shè)置成只讀的呢?這時(shí)因?yàn)槿绻趌ock代碼段中改變obj的值,其它線(xiàn)程就暢通無(wú)阻了,因?yàn)榛コ怄i的對(duì)象變了,object.ReferenceEquals必然返回false。Array 類(lèi)型提供 SyncRoot。許多集合類(lèi)型也提供 SyncRoot。
3)Monitor實(shí)現(xiàn)線(xiàn)程同步
通過(guò)Monitor.Enter() 和 Monitor.Exit()實(shí)現(xiàn)排它鎖的獲取和釋放,獲取之后獨(dú)占資源,不允許其他線(xiàn)程訪(fǎng)問(wèn)。
還有一個(gè)TryEnter方法,請(qǐng)求不到資源時(shí)不會(huì)阻塞等待,可以設(shè)置超時(shí)時(shí)間,獲取不到直接返回false。
public void MonitorSomeThing() { try { Monitor.Enter(obj); dosomething(); } catch(Exception ex) { } finally { Monitor.Exit(obj); } }
4)ReaderWriterLock
當(dāng)對(duì)資源操作讀多寫(xiě)少的時(shí)候,為了提高資源的利用率,讓讀操作鎖為共享鎖,多個(gè)線(xiàn)程可以并發(fā)讀取資源,而寫(xiě)操作為獨(dú)占鎖,只允許一個(gè)線(xiàn)程操作。
class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int, string> innerCache = new Dictionary<int, string>(); public string Read(int key) { cacheLock.EnterReadLock(); try { return innerCache[key]; } finally { cacheLock.ExitReaderLock(); } } public void Add(int key, string value) { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } } public bool AddWithTimeout(int key, string value, int timeout) { if (cacheLock.TryEnterWriteLock(timeout)) { try { innerCache.Add(key, value); } finally { cacheLock.ExitReaderLock(); } return true; } else { return false; } } public AddOrUpdateStatus AddOrUpdate(int key, string value) { cacheLock.EnterUpgradeableReadLock(); try { string result = null; if (innerCache.TryGetValue(key, out result)) { if (result == value) { return AddOrUpdateStatus.Unchanged; } else { cacheLock.EnterWriteLock(); try { innerCache[key] = value; } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Updated; } } else { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Added; } } finally { cacheLock.ExitUpgradeableReadLock(); } } public void Delete(int key) { cacheLock.EnterWriteLock(); try { innerCache.Remove(key); } finally { cacheLock.ExitWriteLock(); } } public enum AddOrUpdateStatus { Added, Updated, Unchanged }; }
5)事件(Event)類(lèi)實(shí)現(xiàn)同步
事件類(lèi)有兩種狀態(tài),終止?fàn)顟B(tài)和非終止?fàn)顟B(tài),終止?fàn)顟B(tài)時(shí)調(diào)用WaitOne可以請(qǐng)求成功,通過(guò)Set將時(shí)間狀態(tài)設(shè)置為終止?fàn)顟B(tài)。
1).AutoResetEvent(自動(dòng)重置事件)
2).ManualResetEvent(手動(dòng)重置事件)
AutoResetEvent和ManualResetEvent這兩個(gè)類(lèi)經(jīng)常用到, 他們的用法很類(lèi)似,但也有區(qū)別。Set方法將信號(hào)置為發(fā)送狀態(tài),Reset方法將信號(hào)置為不發(fā)送狀態(tài),WaitOne等待信號(hào)的發(fā)送。可以通過(guò)構(gòu)造函數(shù)的參數(shù)值來(lái)決定其初始狀態(tài),若為true則非阻塞狀態(tài),為false為阻塞狀態(tài)。如果某個(gè)線(xiàn)程調(diào)用WaitOne方法,則當(dāng)信號(hào)處于發(fā)送狀態(tài)時(shí),該線(xiàn)程會(huì)得到信號(hào), 繼續(xù)向下執(zhí)行。其區(qū)別就在調(diào)用后,AutoResetEvent.WaitOne()每次只允許一個(gè)線(xiàn)程進(jìn)入,當(dāng)某個(gè)線(xiàn)程得到信號(hào)后,AutoResetEvent會(huì)自動(dòng)又將信號(hào)置為不發(fā)送狀態(tài),則其他調(diào)用WaitOne的線(xiàn)程只有繼續(xù)等待.也就是說(shuō),AutoResetEvent一次只喚醒一個(gè)線(xiàn)程;而ManualResetEvent則可以喚醒多個(gè)線(xiàn)程,因?yàn)楫?dāng)某個(gè)線(xiàn)程調(diào)用了ManualResetEvent.Set()方法后,其他調(diào)用WaitOne的線(xiàn)程獲得信號(hào)得以繼續(xù)執(zhí)行,而ManualResetEvent不會(huì)自動(dòng)將信號(hào)置為不發(fā)送。也就是說(shuō),除非手工調(diào)用了ManualResetEvent.Reset()方法,則ManualResetEvent將一直保持有信號(hào)狀態(tài),ManualResetEvent也就可以同時(shí)喚醒多個(gè)線(xiàn)程繼續(xù)執(zhí)行。
6)信號(hào)量(Semaphore)
信號(hào)量是由內(nèi)核對(duì)象維護(hù)的int變量,為0時(shí),線(xiàn)程阻塞,大于0時(shí)解除阻塞,當(dāng)一個(gè)信號(hào)量上的等待線(xiàn)程解除阻塞后,信號(hào)量計(jì)數(shù)+1。
線(xiàn)程通過(guò)WaitOne將信號(hào)量減1,通過(guò)Release將信號(hào)量加1,使用很簡(jiǎn)單。
public Thread thrd; //創(chuàng)建一個(gè)可授權(quán)2個(gè)許可證的信號(hào)量,且初始值為2 static Semaphore sem = new Semaphore(2, 2); public mythread(string name) { thrd = new Thread(this.run); thrd.Name = name; thrd.Start(); } void run() { Console.WriteLine(thrd.Name + "正在等待一個(gè)許可證……"); //申請(qǐng)一個(gè)許可證 sem.WaitOne(); Console.WriteLine(thrd.Name + "申請(qǐng)到許可證……"); for (int i = 0; i < 4 ; i++) { Console.WriteLine(thrd.Name + ": " + i); Thread.Sleep(1000); } Console.WriteLine(thrd.Name + " 釋放許可證……"); //釋放 sem.Release(); } } class mysemaphore { public static void Main() { mythread mythrd1 = new mythread("Thrd #1"); mythread mythrd2 = new mythread("Thrd #2"); mythread mythrd3 = new mythread("Thrd #3"); mythread mythrd4 = new mythread("Thrd #4"); mythrd1.thrd.Join(); mythrd2.thrd.Join(); mythrd3.thrd.Join(); mythrd4.thrd.Join(); } }
7)互斥體(Mutex)
獨(dú)占資源,可以把Mutex看作一個(gè)出租車(chē),乘客看作線(xiàn)程。乘客首先等車(chē),然后上車(chē),最后下車(chē)。當(dāng)一個(gè)乘客在車(chē)上時(shí),其他乘客就只有等他下車(chē)以后才可以上車(chē)。而線(xiàn)程與C# Mutex對(duì)象的關(guān)系也正是如此,線(xiàn)程使用Mutex.WaitOne()方法等待C# Mutex對(duì)象被釋放,如果它等待的C# Mutex對(duì)象被釋放了,它就自動(dòng)擁有這個(gè)對(duì)象,直到它調(diào)用Mutex.ReleaseMutex()方法釋放這個(gè)對(duì)象,而在此期間,其他想要獲取這個(gè)C# Mutex對(duì)象的線(xiàn)程都只有等待。
class Test { /// <summary> /// 應(yīng)用程序的主入口點(diǎn)。 /// </summary> [STAThread] static void Main(string[] args) { bool flag = false; System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", out flag); //第一個(gè)參數(shù):true--給調(diào)用線(xiàn)程賦予互斥體的初始所屬權(quán) //第一個(gè)參數(shù):互斥體的名稱(chēng) //第三個(gè)參數(shù):返回值,如果調(diào)用線(xiàn)程已被授予互斥體的初始所屬權(quán),則返回true if (flag) { Console.Write("Running"); } else { Console.Write("Another is Running"); System.Threading.Thread.Sleep(5000);//線(xiàn)程掛起5秒鐘 Environment.Exit(1);//退出程序 } Console.ReadLine(); } }
8)跨進(jìn)程間的同步
通過(guò)設(shè)置同步對(duì)象的名稱(chēng)就可以實(shí)現(xiàn)系統(tǒng)級(jí)的同步,不同應(yīng)用程序通過(guò)同步對(duì)象的名稱(chēng)識(shí)別不同同步對(duì)象。
static void Main(string[] args) { string MutexName = "InterProcessSyncName"; Mutex SyncNamed; //聲明一個(gè)已命名的互斥對(duì)象 try { SyncNamed = Mutex.OpenExisting(MutexName); //如果此命名互斥對(duì)象已存在則請(qǐng)求打開(kāi) } catch (WaitHandleCannotBeOpenedException) { SyncNamed = new Mutex(false, MutexName); //如果初次運(yùn)行沒(méi)有已命名的互斥對(duì)象則創(chuàng)建一個(gè) } Task MulTesk = new Task ( () => //多任務(wù)并行計(jì)算中的匿名方法,用委托也可以 { for (; ; ) //為了效果明顯而設(shè)計(jì) { Console.WriteLine("當(dāng)前進(jìn)程等待獲取互斥訪(fǎng)問(wèn)權(quán)......"); SyncNamed.WaitOne(); Console.WriteLine("獲取互斥訪(fǎng)問(wèn)權(quán),訪(fǎng)問(wèn)資源完畢,按回車(chē)釋放互斥資料訪(fǎng)問(wèn)權(quán)."); Console.ReadLine(); SyncNamed.ReleaseMutex(); Console.WriteLine("已釋放互斥訪(fǎng)問(wèn)權(quán)。"); } } ); MulTesk.Start(); MulTesk.Wait(); }
9)分布式的同步
可以使用redis任務(wù)隊(duì)列或者redis相關(guān)特性
Parallel.For(0, 1000000, i => { Stopwatch sw1 = new Stopwatch(); sw1.Start(); if (redisHelper.GetRedisOperation().Lock(key)) { var tt = int.Parse(redisHelper.GetRedisOperation().StringGet("calc")); tt++; redisHelper.GetRedisOperation().StringSet("calc", tt.ToString()); redisHelper.GetRedisOperation().UnLock(key); } var v = sw1.ElapsedMilliseconds; if (v >= 10 * 1000) { Console.Write("f"); } sw1.Stop(); });
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- C#使用LOCK實(shí)現(xiàn)線(xiàn)程同步
- C#多線(xiàn)程之線(xiàn)程同步
- C#多線(xiàn)程之線(xiàn)程同步WaitHandle
- C#多線(xiàn)程系列之進(jìn)程同步Mutex類(lèi)
- c#中多線(xiàn)程間的同步示例詳解
- c# 進(jìn)程之間的線(xiàn)程同步
- 淺析c# 線(xiàn)程同步
- C# 線(xiàn)程同步的方法
- 詳解c# 線(xiàn)程同步
- 深入分析C# 線(xiàn)程同步
- C#線(xiàn)程同步的幾種方法總結(jié)
- C# 線(xiàn)程同步詳解
- C#使用Monitor類(lèi)實(shí)現(xiàn)線(xiàn)程同步
相關(guān)文章
深入分析C#中WinForm控件之Dock順序調(diào)整的詳解
本篇文章是對(duì)C#中WinForm控件之Dock順序調(diào)整進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05超簡(jiǎn)單C#獲取帶漢字的字符串真實(shí)長(zhǎng)度(單個(gè)英文長(zhǎng)度為1,單個(gè)中文長(zhǎng)度為2)
正常情況下,我們是直接去string的length的,但是漢字是有兩個(gè)字節(jié)的,所以直接用length是錯(cuò)的2018-03-03

C#實(shí)現(xiàn)塊狀鏈表的項(xiàng)目實(shí)踐