C# 多線程及同步簡介示例

       60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨着計算機技術的發展,進程出現了不少弊端,一是因爲進程是資源擁有者,建立、撤消與切換存在較大的時空開銷,所以須要引入輕型進程;二是因爲對稱多處理機(SMP)出現,能夠知足多個運行單位,而多個進程並行開銷過大。
所以在80年代,出現了能獨立運行的基本單位——線程(Threads)。
       線程,有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令 指針(PC), 寄存器集合和 堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。一個線程能夠建立和撤消另外一個線程,同一進程中的多個線程之間能夠併發執行。因爲線程之間的相互制約,導致線程在運行中呈現出間斷性。線程也有 就緒阻塞運行三種基本狀態。就緒狀態是指線程具有運行的全部條件,邏輯上能夠運行,在等待處理機;運行狀態是指線程佔有處理機正在運行;阻塞狀態是指線程在等待一個事件(如某個信號量),邏輯上不可執行。每個程序都至少有一個線程,若程序只有一個線程,那就是程序自己。
       線程是程序中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位指 運行中的程序的調度單位。在單個程序中同時運行多個線程完成不一樣的工做,稱爲 多線程

 

1、線程簡義html

一、進程與線程:進程做爲操做系統執行程序的基本單位,擁有應用程序的資源,進程包含線程,進程的資源被線程共享,線程不擁有資源。git

二、前臺線程和後臺線程:經過Thread類新建線程默認爲前臺線程。當全部前臺線程關閉時,全部的後臺線程也會被直接終止,不會拋出異常。github

三、掛起(Suspend)和喚醒(Resume):因爲線程的執行順序和程序的執行狀況不可預知,因此使用掛起和喚醒容易發生死鎖的狀況,在實際應用中應該儘可能少用。redis

四、阻塞線程:Join,阻塞調用線程,直到該線程終止。多線程

五、終止線程:Abort:拋出 ThreadAbortException 異常讓線程終止,終止後的線程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線程終止,經過捕獲異常能夠繼續執行。併發

六、線程優先級:AboveNormal BelowNormal Highest Lowest Normal,默認爲Normal。異步

2、線程的使用分佈式

線程函數經過委託傳遞,能夠不帶參數,也能夠帶參數(只能有一個參數),能夠用一個類或結構體封裝參數。函數

 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 }

 

3、線程池性能

因爲線程的建立和銷燬須要耗費必定的開銷,過多的使用線程會形成內存資源的浪費,出於對性能的考慮,因而引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,而後委派給線程池的一個線程執行,線程執行完不會被當即銷燬,這樣既能夠在後臺執行任務,又能夠減小線程建立和銷燬所帶來的開銷。

線程池線程默認爲後臺線程(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     }

 

4、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     }

 

5、委託異步執行

委託的異步調用: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     }

 

6、線程同步

  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/若是發現本文有什麼問題和任何建議,也隨時歡迎交流~

相關文章
相關標籤/搜索