60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨著電腦技術的發展,進程出現了很多弊端,一是由於進程是資源擁有者,創建、撤消與切換存在較大的時空開銷,因此需要引入輕型進程;二是由於對稱多處理機(SMP)出現,可以滿足多個運行單位,而多個進程並行開銷過大。 因此在80年代,出現了能獨立運 ...
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。
二、線程的使用
線程函數通過委托傳遞,可以不帶參數,也可以帶參數(只能有一個參數),可以用一個類或結構體封裝參數。
1 namespace Test 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Thread t1 = new Thread(new ThreadStart(TestMethod)); 8 Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod)); 9 t1.IsBackground = true; 10 t2.IsBackground = true; 11 t1.Start(); 12 t2.Start("hello"); 13 Console.ReadKey(); 14 } 15 16 public static void TestMethod() 17 { 18 Console.WriteLine("不帶參數的線程函數"); 19 } 20 21 public static void TestMethod(object data) 22 { 23 string datastr = data as string; 24 Console.WriteLine("帶參數的線程函數,參數為:{0}", datastr); 25 } 26 } 27 }
三、線程池
由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成記憶體資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在後臺執行任務,又可以減少線程創建和銷毀所帶來的開銷。
線程池線程預設為後臺線程(IsBackground)。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //將工作項加入到線程池隊列中,這裡可以傳遞一個線程參數 6 ThreadPool.QueueUserWorkItem(TestMethod, "Hello"); 7 Console.ReadKey(); 8 } 9 10 public static void TestMethod(object data) 11 { 12 string datastr = data as string; 13 Console.WriteLine(datastr); 14 } 15 }
四、Task類
使用ThreadPool的QueueUserWorkItem()方法發起一次非同步的線程執行很簡單,但是該方法最大的問題是沒有一個內建的機制讓你知道操作什麼時候完成,有沒有一個內建的機制在操作完成後獲得一個返回值。為此,可以使用System.Threading.Tasks中的Task類。
構造一個Task<TResult>對象,併為泛型TResult參數傳遞一個操作的返回類型。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000); 6 t.Start(); 7 t.Wait(); 8 Console.WriteLine(t.Result); 9 Console.ReadKey(); 10 } 11 12 private static Int32 Sum(Int32 n) 13 { 14 Int32 sum = 0; 15 for (; n > 0; --n) 16 checked{ sum += n;} //結果太大,拋出異常 17 return sum; 18 } 19 }
一個任務完成時,自動啟動一個新任務。
一個任務完成後,它可以啟動另一個任務,下麵重寫了前面的代碼,不阻塞任何線程。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000); 6 t.Start(); 7 //t.Wait(); 8 Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result)); 9 Console.ReadKey(); 10 } 11 12 private static Int32 Sum(Int32 n) 13 { 14 Int32 sum = 0; 15 for (; n > 0; --n) 16 checked{ sum += n;} //結果溢出,拋出異常 17 return sum; 18 } 19 }
五、委托非同步執行
委托的非同步調用:BeginInvoke() 和 EndInvoke()
1 public delegate string MyDelegate(object data); 2 class Program 3 { 4 static void Main(string[] args) 5 { 6 MyDelegate mydelegate = new MyDelegate(TestMethod); 7 IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param"); 8 9 //非同步執行完成 10 string resultstr = mydelegate.EndInvoke(result); 11 } 12 13 //線程函數 14 public static string TestMethod(object data) 15 { 16 string datastr = data as string; 17 return datastr; 18 } 19 20 //非同步回調函數 21 public static void TestCallback(IAsyncResult data) 22 { 23 Console.WriteLine(data.AsyncState); 24 } 25 }
六、線程同步
1)原子操作(Interlocked):幫助保護免受計劃程式切換上下文時某個線程正在更新可以由其他線程訪問的變數或者在單獨的處理器上同時執行兩個線程就可能出現的錯誤。 此類的成員不會引發異常。
1 class Program 2 { 3 static int counter = 1; 4 5 static void Main(string[] args) 6 { 7 Thread t1 = new Thread(new ThreadStart(F1)); 8 Thread t2 = new Thread(new ThreadStart(F2)); 9 10 t1.Start(); 11 t2.Start(); 12 13 t1.Join(); 14 t2.Join(); 15 16 System.Console.ReadKey(); 17 } 18 19 static void F1() 20 { 21 for (int i = 0; i < 5; i++) 22 { 23 Interlocked.Increment(ref counter); 24 System.Console.WriteLine("Counter++ {0}", counter); 25 Thread.Sleep(10); 26 } 27 } 28 29 static void F2() 30 { 31 for (int i = 0; i < 5; i++) 32 { 33 Interlocked.Decrement(ref counter); 34 System.Console.WriteLine("Counter-- {0}", counter); 35 Thread.Sleep(10); 36 } 37 } 38 }
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。
1 public void MonitorSomeThing() 2 { 3 try 4 { 5 Monitor.Enter(obj); 6 dosomething(); 7 } 8 catch(Exception ex) 9 { 10 11 } 12 finally 13 { 14 Monitor.Exit(obj); 15 } 16 }
4)ReaderWriterLock
當對資源操作讀多寫少的時候,為了提高資源的利用率,讓讀操作鎖為共用鎖,多個線程可以併發讀取資源,而寫操作為獨占鎖,只允許一個線程操作。
1 class SynchronizedCache 2 { 3 private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); 4 private Dictionary<int, string> innerCache = new Dictionary<int, string>(); 5 6 public string Read(int key) 7 { 8 cacheLock.EnterReadLock(); 9 try 10 { 11 return innerCache[key]; 12 } 13 finally 14 { 15 cacheLock.ExitReaderLock(); 16 } 17 } 18 19 public void Add(int key, string value) 20 { 21 cacheLock.EnterWriteLock(); 22 try 23 { 24 innerCache.Add(key, value); 25 } 26 finally 27 { 28 cacheLock.ExitWriteLock(); 29 } 30 } 31 32 public bool AddWithTimeout(int key, string value, int timeout) 33 { 34 if (cacheLock.TryEnterWriteLock(timeout)) 35 { 36 try 37 { 38 innerCache.Add(key, value); 39 } 40 finally 41 { 42 cacheLock.ExitReaderLock(); 43 } 44 return true; 45 } 46 else 47 { 48 return false; 49 } 50 } 51 52 public AddOrUpdateStatus AddOrUpdate(int key, string value) 53 { 54 cacheLock.EnterUpgradeableReadLock(); 55 try 56 { 57 string result = null; 58 if (innerCache.TryGetValue(key, out result)) 59 { 60 if (result == value) 61 { 62 return AddOrUpdateStatus.Unchanged; 63 } 64 else 65 { 66 cacheLock.EnterWriteLock(); 67 try 68 { 69 innerCache[key] = value; 70 } 71 finally 72 { 73 cacheLock.ExitWriteLock(); 74 } 75 return AddOrUpdateStatus.Updated; 76 } 77 } 78 else 79 { 80 cacheLock.EnterWriteLock(); 81 try 82 { 83 innerCache.Add(key, value); 84 } 85 finally 86 { 87 cacheLock.ExitWriteLock(); 88 } 89 return AddOrUpdateStatus.Added; 90 } 91 } 92 finally 93 { 94 cacheLock.ExitUpgradeableReadLock(); 95 } 96 } 97 98 public void Delete(int key) 99 { 100 cacheLock.EnterWriteLock(); 101 try 102 { 103 innerCache.Remove(key); 104 } 105 finally 106 { 107 cacheLock.ExitWriteLock(); 108 } 109 } 110 111 public enum AddOrUpdateStatus 112 { 113 Added, 114 Updated, 115 Unchanged 116 }; 117 }
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,使用很簡單。
1 public Thread thrd; 2 //創建一個可授權2個許可證的信號量,且初始值為2 3 static Semaphore sem = new Semaphore(2, 2); 4 5 public mythread(string name) 6 { 7 thrd = new Thread(this.run); 8 thrd.Name = name; 9 thrd.Start(); 10 } 11 void run() 12 { 13 Console.WriteLine(thrd.Name + "正在等待一個許可證……"); 14 //申請一個許可證 15 sem.WaitOne(); 16 Console.WriteLine(thrd.Name + "申請到許可證……"); 17 for (int i = 0; i < 4 ; i++) 18 { 19 Console.WriteLine(thrd.Name + ": " + i); 20 Thread.Sleep(1000); 21 } 22 Console.WriteLine(thrd.Name + " 釋放許可證……"); 23 //釋放 24 sem.Release(); 25 } 26 } 27 28 class mysemaphore 29 { 30 public static void Main() 31 { 32 mythread mythrd1 = new mythread("Thrd #1"); 33 mythread mythrd2 = new mythread("Thrd #2"); 34 mythread mythrd3 = new mythread("Thrd #3"); 35 mythread mythrd4 = new mythread("Thrd #4"); 36 mythrd1.thrd.Join(); 37 mythrd2.thrd.Join(); 38 mythrd3.thrd.Join(); 39 mythrd4.thrd.Join(); 40 } 41 }
7)互斥體(Mutex)
獨占資源,可以把Mutex看作一個計程車,乘客看作線程。乘客首先等車,然後上車,最後下車。當一個乘客在車上時,其他乘客就只有等他下車以後才可以上車。而線程與C# Mutex對象的關係也正是如此,線程使用Mutex.WaitOne()方法等待C# Mutex對象被釋放,如果它等待的C# Mutex對象被釋放了,它就自動擁有這個對象,直到它調用Mutex.ReleaseMutex()方法釋放這個對象,而在此期間,其他想要獲取這個C# Mutex對象的線程都只有等待。
1 class Test 2 { 3 /// <summary> 4 /// 應用程式的主入口點。 5 /// </summary> 6 [STAThread] 7 static void Main(string[] args) 8 { 9 bool flag = false; 10 System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", out flag); 11 //第一個參數:true--給調用線程賦予互斥體的初始所屬權 12 //第一個參數:互斥體的名稱 13 //第三個參數:返回值,如果調用線程已被授予互斥體的初始所屬權,則返回true 14 if (flag) 15 { 16 Console.Write("Running"); 17 } 18 else 19 { 20 Console.Write("Another is Running"); 21 System.Threading.Thread.Sleep(5000);//線程掛起5秒鐘 22 Environment.Exit(1);//退出程式 23 } 24 Console.ReadLine(); 25 } 26 }
8)跨進程間的同步
通過設置同步對象的名稱就可以實現系統級的同步,不同應用程式通過同步對象的名稱識別不同同步對象。
1 static void Main(string[] args) 2 { 3 string MutexName = "InterProcessSyncName"; 4 Mutex SyncNamed; //聲明一個已命名的互斥對象 5 try 6 { 7 SyncNamed = Mutex.OpenExisting(MutexName); //如果此命名互斥對象已存在則請求打開 8 } 9 catch (WaitHandleCannotBeOpenedException) 10 { 11 SyncNamed = new Mutex(false, MutexName); //如果初次運行沒有已命名的互斥對象則創建一個 12 } 13 Task MulTesk = new Task 14 ( 15 () => //多任務並行計算中的匿名方法,用委托也可以 16 { 17 for (; ; ) //為了效果明顯而設計 18 { 19 Console.WriteLine("當前進程等待獲取互斥訪問權......"); 20 SyncNamed.WaitOne(); 21 Console.WriteLine("獲取互斥訪問權,訪問資源完畢,按回車釋放互斥資料訪問權."); 22 Console.ReadLine(); 23 SyncNamed.ReleaseMutex(); 24 Console.WriteLine("已釋放互斥訪問權。"); 25 } 26 } 27 ); 28 MulTesk.Start(); 29 MulTesk.Wait(); 30 }
9)分散式的同步
可以使用redis任務隊列或者redis相關特性
1 Parallel.For(0, 1000000, i => 2 { 3 Stopwatch sw1 = new Stopwatch(); 4 sw1.Start(); 5 6 if (redisHelper.GetRedisOperation().Lock(key)) 7 { 8 var tt = int.Parse(redisHelper.GetRedisOperation().StringGet("calc")); 9 10 tt++; 11 12 redisHelper.GetRedisOperation().StringSet("calc", tt.ToString()); 13 14 redisHelper.GetRedisOperation().UnLock(key); 15 } 16 var v = sw1.ElapsedMilliseconds; 17 if (v >= 10 * 1000) 18 { 19 Console.Write("f"); 20 } 21 sw1.Stop(); 22 });
轉載請標明本文來源:http://www.cnblogs.com/yswenli/p/7421475.html
更多內容歡迎star作者的github:https://github.com/yswenli/
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~