您好,登錄后才能下訂單哦!
60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨著計算機技術的發展,進程出現了很多弊端,一是由于進程是資源擁有者,創建、撤消與切換存在較大的時空開銷,因此需要引入輕型進程;二是由于對稱多處理機(SMP)出現,可以滿足多個運行單位,而多個進程并行開銷過大。
因此在80年代,出現了能獨立運行的基本單位——線程(Threads)。
線程,有時被稱為輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以并發執行。由于線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。就緒狀態是指線程具備運行的所有條件,邏輯上可以運行,在等待處理機;運行狀態是指線程占有處理機正在運行;阻塞狀態是指線程在等待一個事件(如某個信號量),邏輯上不可執行。每一個程序都至少有一個線程,若程序只有一個線程,那就是程序本身。
線程是程序中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位指運行中的程序的調度單位。在單個程序中同時運行多個線程完成不同的工作,稱為多線程。
一、線程簡義
1、進程與線程:進程作為操作系統執行程序的基本單位,擁有應用程序的資源,進程包含線程,進程的資源被線程共享,線程不擁有資源。
2、前臺線程和后臺線程:通過Thread類新建線程默認為前臺線程。當所有前臺線程關閉時,所有的后臺線程也會被直接終止,不會拋出異常。
3、掛起(Suspend)和喚醒(Resume):由于線程的執行順序和程序的執行情況不可預知,所以使用掛起和喚醒容易發生死鎖的情況,在實際應用中應該盡量少用。
4、阻塞線程:Join,阻塞調用線程,直到該線程終止。
5、終止線程:Abort:拋出 ThreadAbortException 異常讓線程終止,終止后的線程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線程終止,通過捕獲異常可以繼續執行。
6、線程優先級:AboveNormal BelowNormal Highest Lowest Normal,默認為Normal。
二、線程的使用
線程函數通過委托傳遞,可以不帶參數,也可以帶參數(只能有一個參數),可以用一個類或結構體封裝參數。
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("不帶參數的線程函數"); } public static void TestMethod(object data) { string datastr = data as string; Console.WriteLine("帶參數的線程函數,參數為:{0}", datastr); } } }
三、線程池
由于線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成內存資源的浪費,出于對性能的考慮,于是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然后委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在后臺執行任務,又可以減少線程創建和銷毀所帶來的開銷。
線程池線程默認為后臺線程(IsBackground)。
class Program { static void Main(string[] args) { //將工作項加入到線程池隊列中,這里可以傳遞一個線程參數 ThreadPool.QueueUserWorkItem(TestMethod, "Hello"); Console.ReadKey(); } public static void TestMethod(object data) { string datastr = data as string; Console.WriteLine(datastr); } }
四、Task類
使用ThreadPool的QueueUserWorkItem()方法發起一次異步的線程執行很簡單,但是該方法最大的問題是沒有一個內建的機制讓你知道操作什么時候完成,有沒有一個內建的機制在操作完成后獲得一個返回值。為此,可以使用System.Threading.Tasks中的Task類。
構造一個Task<TResult>對象,并為泛型TResult參數傳遞一個操作的返回類型。
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;} //結果太大,拋出異常 return sum; } }
一個任務完成時,自動啟動一個新任務。
一個任務完成后,它可以啟動另一個任務,下面重寫了前面的代碼,不阻塞任何線程。
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;} //結果溢出,拋出異常 return sum; } }
五、委托異步執行
委托的異步調用: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"); //異步執行完成 string resultstr = mydelegate.EndInvoke(result); } //線程函數 public static string TestMethod(object data) { string datastr = data as string; return datastr; } //異步回調函數 public static void TestCallback(IAsyncResult data) { Console.WriteLine(data.AsyncState); } }
六、線程同步
1)原子操作(Interlocked):幫助保護免受計劃程序切換上下文時某個線程正在更新可以由其他線程訪問的變量或者在單獨的處理器上同時執行兩個線程就可能出現的錯誤。 此類的成員不會引發異常。
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()語句:避免鎖定public類型,否則實例將超出代碼控制的范圍,定義private對象來鎖定。而自定義類推薦用私有的只讀靜態對象,比如:private static readonly object obj = new object();為什么要設置成只讀的呢?這時因為如果在lock代碼段中改變obj的值,其它線程就暢通無阻了,因為互斥鎖的對象變了,object.ReferenceEquals必然返回false。Array 類型提供 SyncRoot。許多集合類型也提供 SyncRoot。
3)Monitor實現線程同步
通過Monitor.Enter() 和 Monitor.Exit()實現排它鎖的獲取和釋放,獲取之后獨占資源,不允許其他線程訪問。
還有一個TryEnter方法,請求不到資源時不會阻塞等待,可以設置超時時間,獲取不到直接返回false。
public void MonitorSomeThing() { try { Monitor.Enter(obj); dosomething(); } catch(Exception ex) { } finally { Monitor.Exit(obj); } }
4)ReaderWriterLock
當對資源操作讀多寫少的時候,為了提高資源的利用率,讓讀操作鎖為共享鎖,多個線程可以并發讀取資源,而寫操作為獨占鎖,只允許一個線程操作。
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)類實現同步
事件類有兩種狀態,終止狀態和非終止狀態,終止狀態時調用WaitOne可以請求成功,通過Set將時間狀態設置為終止狀態。
1).AutoResetEvent(自動重置事件)
2).ManualResetEvent(手動重置事件)
AutoResetEvent和ManualResetEvent這兩個類經常用到, 他們的用法很類似,但也有區別。Set方法將信號置為發送狀態,Reset方法將信號置為不發送狀態,WaitOne等待信號的發送。可以通過構造函數的參數值來決定其初始狀態,若為true則非阻塞狀態,為false為阻塞狀態。如果某個線程調用WaitOne方法,則當信號處于發送狀態時,該線程會得到信號, 繼續向下執行。其區別就在調用后,AutoResetEvent.WaitOne()每次只允許一個線程進入,當某個線程得到信號后,AutoResetEvent會自動又將信號置為不發送狀態,則其他調用WaitOne的線程只有繼續等待.也就是說,AutoResetEvent一次只喚醒一個線程;而ManualResetEvent則可以喚醒多個線程,因為當某個線程調用了ManualResetEvent.Set()方法后,其他調用WaitOne的線程獲得信號得以繼續執行,而ManualResetEvent不會自動將信號置為不發送。也就是說,除非手工調用了ManualResetEvent.Reset()方法,則ManualResetEvent將一直保持有信號狀態,ManualResetEvent也就可以同時喚醒多個線程繼續執行。
6)信號量(Semaphore)
信號量是由內核對象維護的int變量,為0時,線程阻塞,大于0時解除阻塞,當一個信號量上的等待線程解除阻塞后,信號量計數+1。
線程通過WaitOne將信號量減1,通過Release將信號量加1,使用很簡單。
public Thread thrd; //創建一個可授權2個許可證的信號量,且初始值為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 + "正在等待一個許可證……"); //申請一個許可證 sem.WaitOne(); Console.WriteLine(thrd.Name + "申請到許可證……"); 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)
獨占資源,可以把Mutex看作一個出租車,乘客看作線程。乘客首先等車,然后上車,最后下車。當一個乘客在車上時,其他乘客就只有等他下車以后才可以上車。而線程與C# Mutex對象的關系也正是如此,線程使用Mutex.WaitOne()方法等待C# Mutex對象被釋放,如果它等待的C# Mutex對象被釋放了,它就自動擁有這個對象,直到它調用Mutex.ReleaseMutex()方法釋放這個對象,而在此期間,其他想要獲取這個C# Mutex對象的線程都只有等待。
class Test { /// <summary> /// 應用程序的主入口點。 /// </summary> [STAThread] static void Main(string[] args) { bool flag = false; System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", out flag); //第一個參數:true--給調用線程賦予互斥體的初始所屬權 //第一個參數:互斥體的名稱 //第三個參數:返回值,如果調用線程已被授予互斥體的初始所屬權,則返回true if (flag) { Console.Write("Running"); } else { Console.Write("Another is Running"); System.Threading.Thread.Sleep(5000);//線程掛起5秒鐘 Environment.Exit(1);//退出程序 } Console.ReadLine(); } }
8)跨進程間的同步
通過設置同步對象的名稱就可以實現系統級的同步,不同應用程序通過同步對象的名稱識別不同同步對象。
static void Main(string[] args) { string MutexName = "InterProcessSyncName"; Mutex SyncNamed; //聲明一個已命名的互斥對象 try { SyncNamed = Mutex.OpenExisting(MutexName); //如果此命名互斥對象已存在則請求打開 } catch (WaitHandleCannotBeOpenedException) { SyncNamed = new Mutex(false, MutexName); //如果初次運行沒有已命名的互斥對象則創建一個 } Task MulTesk = new Task ( () => //多任務并行計算中的匿名方法,用委托也可以 { for (; ; ) //為了效果明顯而設計 { Console.WriteLine("當前進程等待獲取互斥訪問權......"); SyncNamed.WaitOne(); Console.WriteLine("獲取互斥訪問權,訪問資源完畢,按回車釋放互斥資料訪問權."); Console.ReadLine(); SyncNamed.ReleaseMutex(); Console.WriteLine("已釋放互斥訪問權。"); } } ); MulTesk.Start(); MulTesk.Wait(); }
9)分布式的同步
可以使用redis任務隊列或者redis相關特性
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(); });
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。