溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

.Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)

發(fā)布時(shí)間:2020-10-15 18:14:57 來(lái)源:網(wǎng)絡(luò) 閱讀:594 作者:jinyuan0829 欄目:編程語(yǔ)言

 .Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)

2.同步線(xiàn)程

  • 手動(dòng)同步

    • 監(jiān)視器

    • 互斥

    • 可等待事件

 

同步線(xiàn)程

所有的.NET組件都支持在多線(xiàn)程的環(huán)境中運(yùn)行,可以被多個(gè)線(xiàn)程并發(fā)訪(fǎng)問(wèn),如果沒(méi)有線(xiàn)程同步,這樣的后果是當(dāng)多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn) 對(duì)象狀態(tài)時(shí),對(duì)象的狀態(tài)可能被破壞,造成不一致性。.NET提供了兩種方法來(lái)避免這樣的問(wèn)題,使得我們?cè)O(shè)計(jì)的組件更加健壯。 第一種是自動(dòng)同步,讓你使用一個(gè)屬性來(lái)修飾組件,這樣就可以把組件交給.NET了,同步的事情也就交給了.NET。 第二種是手動(dòng)同步,這是讓你使用.NET提供的同步對(duì)象來(lái)實(shí)現(xiàn)線(xiàn)程同步,也不是太復(fù)雜,本篇將會(huì)對(duì)手動(dòng)同步來(lái)稍作講解。

 

2.1 手動(dòng)同步

    .NET手動(dòng)同步提供了一套豐富的同步鎖,上一節(jié)說(shuō)到同步域,同步域事實(shí)上是一個(gè)巨大的宏鎖,而手動(dòng)同步則提供了 對(duì)被鎖對(duì)象的細(xì)粒度控制,可以控制訪(fǎng)問(wèn)對(duì)象、單一成員甚至是單行的代碼。這樣的好處就是有可能的提高系統(tǒng)的性能和吞吐量。

2.1.1 監(jiān)視器

監(jiān)視器是一種只能和引用類(lèi)型一塊工作的鎖。

2.1.1-1

 1     public class ManualSynchronization
 2     {
 3         public void DoSomeThing()
 4         {
 5             for (int i = 0; i < 100; i++)
 6             {
 7                 Console.WriteLine(i.ToString());
 8             }
 9         }
10     }
 1 ManualSynchronization monitorcase = new ManualSynchronization();
 2 
 3 Monitor.Enter(monitorcase);
 4 try
 5 {
 6     monitorcase.DoSomeThing();
 7 }
 8 finally
 9 {
10     Monitor.Exit(monitorcase);
11 }

任何線(xiàn)程的任何對(duì)象都可以調(diào)用Enter()方法來(lái)鎖定對(duì)象,如果Monitor正在被一個(gè)線(xiàn)程使用,而這個(gè)時(shí)候又有一個(gè)線(xiàn)程來(lái)請(qǐng)求對(duì)象Enter(),這樣就會(huì)使第二個(gè)線(xiàn)程阻塞,直到第一個(gè)線(xiàn)程調(diào)用Exit(),如果這時(shí)有多個(gè)線(xiàn)程請(qǐng)求對(duì)象Enter(),它們就會(huì)被放置在一個(gè)叫做鎖隊(duì)列的隊(duì)列里,并依照隊(duì)列的順序獲得服務(wù)順序。

你還可以使用Monitor類(lèi)為靜態(tài)類(lèi)方法或靜態(tài)屬性提供安全線(xiàn)程訪(fǎng)問(wèn),方法是讓Monitor鎖定該類(lèi)型,而不是一個(gè)實(shí)例:

2.1.1-2

 1     public class ManualSynchronization
 2     {
 3         public static void SDoSomeThing()
 4         {
 5             for (int i = 0; i < 100; i++)
 6             {
 7                 Console.WriteLine(i.ToString());
 8             }
 9         }
10     }
1            Monitor.Enter(typeof(ManualSynchronization));
2            try
3            {
4                ManualSynchronization.SDoSomeThing();
5            }
6            finally
7            {
8                Monitor.Exit(typeof(ManualSynchronization));
9            }

在C#中為了簡(jiǎn)化這樣的寫(xiě)法,提供了lock語(yǔ)句,使編譯器在try/finally語(yǔ)句中自動(dòng)產(chǎn)生對(duì)Enter()和Exit()的調(diào)用。

比如你寫(xiě)下這樣的代碼等同于2.1.1-1的示例代碼:

2.1.1-3

1 ManualSynchronization monitorcase = new ManualSynchronization();
2 lock(monitorcase)
3 {
4     monitorcase.DoSomeThing();
5 }

像上面的代碼這樣寫(xiě)看似沒(méi)什么問(wèn)題了,因?yàn)檫@個(gè)lock所定對(duì)象實(shí)例或者是對(duì)象類(lèi)型,是根據(jù)客戶(hù)端開(kāi)發(fā)者的判斷而定的,這樣的鎖定方式與客戶(hù)端耦合度大,看下以下代碼:

2.1.1-4

 1     public class ManualSynchronization
 2     {
 3         public void DoSomeThing()
 4         {
 5             lock (this)
 6             {
 7                 for (int i = 0; i < 100; i++)
 8                 {
 9                     Console.WriteLine(i.ToString());
10                 }
11             }
12         }
13     }
1 ManualSynchronization monitorcase = new ManualSynchronization();
2 monitorcase.DoSomeThing();

這樣感覺(jué)是不是舒服不少,這就是方法同步了,.NET內(nèi)部也對(duì)它提供了支持,定義在System.Runtime.CompilerServices命名空間里的MethodImpl方法屬性接受一個(gè)MethodImplOptions類(lèi)型的枚舉。其中一個(gè)枚舉值是MethodImplOptions.Synchronized。當(dāng)運(yùn)行這個(gè)枚舉值的時(shí)候,編輯器就指示.NET運(yùn)行時(shí)在方法入口鎖定對(duì)象,語(yǔ)義和2.1.1-4的代碼斷相同:

2.1.1-5

1     public class ManualSynchronization
2     {
3         [MethodImpl( MethodImplOptions.Synchronized)]
4         public void DoSomeThingSynchroniezd()
5         {
6             Console.WriteLine("studycase");
7         }
8     }
2.1.2 互斥

這一個(gè)小節(jié)要講到的是Mutex類(lèi),它是從WaitHandle派生的類(lèi),它保證了各個(gè)線(xiàn)程在某個(gè)資源或代碼塊上相互排斥。

 2.1.2-1

 1     public class MutexDom:IDisposable
 2     {
 3         public MutexDom(){}
 4         private int _Num = 0;
 5         public int Num
 6         {
 7             get
 8             {
 9                 return _Num;
10             }
11             set
12             {
13                 _Num = value;
14             }
15         }
16         public void Dom()
17         {
18                  for (int i = 0; i < 100; i++)
19                 {
20                     Num = Num + i;
21                     Console.WriteLine(Thread.CurrentThread.Name + "_" + Num.ToString() +"_"+Thread.CurrentThread.ManagedThreadId.ToString());
22                 }
23 
24             
25         }
26         public void Dispose()
27         {
28          
29         }
30 
31         public static void Test()
32         {
33             MutexDom mutexDom=new MutexDom();
34             ThreadStart threadStart=new ThreadStart(mutexDom.Dom);
35             Thread thread1 = new Thread(threadStart);
36             thread1.Name = "Thread_One";
37             Thread thread2 = new Thread(threadStart);
38             thread2.Name = "Thread_Two";
39             thread1.Start();
40             thread2.Start();
41         }
42     }

MutexDom.Test();啟動(dòng)測(cè)試,我所希望的效果是Dom()方法是有序的執(zhí)行的,而我用了一個(gè)int類(lèi)型的Nun屬性來(lái)作為計(jì)數(shù)器,那我們就一起來(lái)看一下結(jié)果吧(可能每次運(yùn)行結(jié)果不一樣)

.Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)

我所期望的在線(xiàn)程Thread_One中執(zhí)行0遞增至99的值時(shí)4950,而在結(jié)果中已經(jīng)超出了這個(gè)范圍,這說(shuō)明了什么?說(shuō)明了兩個(gè)線(xiàn)程在交替的對(duì)Num進(jìn)行操作。修改一下代碼,再來(lái)看一下:

 2.1.2-2

 1     public class MutexDom:IDisposable
 2     {
 3         private Mutex _Mutex;
 4         public MutexDom()
 5         {
 6             _Mutex = new Mutex();
 7         }
 8         private int _Num = 0;
 9         public int Num
10         {
11             get
12             {
13                 return _Num;
14             }
15             set
16             {
17                 _Num = value;
18             }
19         }
20         public void Dom()
21         {
22             _Mutex.WaitOne();//如果當(dāng)前資源被占用 則等待占用它的線(xiàn)程發(fā)送消息
23             try
24             {
25                 for (int i = 0; i < 100; i++)
26                 {
27                     Num = Num + i;
28                     Console.WriteLine(Thread.CurrentThread.Name + "_" + Num.ToString() +"_"+Thread.CurrentThread.ManagedThreadId.ToString());
29                 }
30             }
31             finally
32             {
33                 _Mutex.ReleaseMutex();
34             }
35             
36         }
37         public void Dispose()
38         {
39             _Mutex.Close();
40         }
41 
42         public static void Test()
43         {
44             MutexDom mutexDom=new MutexDom();
45             ThreadStart threadStart=new ThreadStart(mutexDom.Dom);
46             Thread thread1 = new Thread(threadStart);
47             thread1.Name = "Thread_One";
48             Thread thread2 = new Thread(threadStart);
49             thread2.Name = "Thread_Two";
50             
51             thread1.Start();
52             thread2.Start();
53             
54         }
55     }

.Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)

從結(jié)果中得出,是線(xiàn)程Thread_Two先執(zhí)行的,這個(gè)沒(méi)關(guān)系,只要看它的結(jié)果值就行了,這就說(shuō)明了,在線(xiàn)程"Thread_Two"執(zhí)行對(duì)Dom()方法操作的時(shí)候"Thread_One"是肯定已經(jīng)啟動(dòng)了的,而且是在等待"Thread_Two"的釋放消息,這樣就保持了對(duì)象狀態(tài)的一致性,這個(gè)時(shí)候"Thread_One"是在一個(gè)等待隊(duì)列中的。如果這個(gè)時(shí)候"Thread_One"調(diào)用ReleaseMutex()方法,是會(huì)報(bào)錯(cuò)的,因?yàn)镽eleaseMutex()方法是只能當(dāng)前所占有的線(xiàn)程來(lái)進(jìn)行釋放,互斥就這樣完成了。

2.1.3 可等待事件

EventWaitHandle類(lèi)派生于WaitHandle,被用于跨線(xiàn)程通知事件。 它有兩種狀態(tài):信號(hào)已發(fā)狀態(tài)、信號(hào)未發(fā)狀態(tài)。 Set()方法和 Reset()方法分別把句柄狀態(tài)設(shè)置為信號(hào)已發(fā)或信號(hào)未發(fā)。 它有兩種使用方式,一種是手動(dòng)重置,還有一種是自動(dòng)重置。是通過(guò)給構(gòu)造函數(shù)提供一個(gè)EventResetMode類(lèi)型的枚舉值,

1     public enum EventResetMode
2     {
3        AutoReset,
4        ManualReset
5     }

.NET提供了EventWaitHandle的兩個(gè)強(qiáng)類(lèi)型子類(lèi),定義如下:

 1     public class ManualResetEvent:EventWaitHandle
 2     {
 3         public ManualResetEvent(bool initialState):base(initialState,EventResetMode.ManualReset)
 4         {}
 5     }
 6     public sealed class AutoResetEvent : EventWaitHandle
 7     {
 8         public AutoResetEvent(bool initialState):base(initialState,EventResetMode.AutoReset)
 9         {}
10     }

先來(lái)看一下手動(dòng)重置:

2.1.3-1

 1     public class EventDom:IDisposable
 2     {
 3         ManualResetEvent _WaitHandle;
 4         public EventDom()
 5         {
 6             _WaitHandle = new ManualResetEvent(true);
 7 
 8             Thread thread = new Thread(DoWork);
 9             thread.Start();
10         }
11         private void DoWork()
12         {
13             int num = 0;
14             while (true)
15             {
16                 _WaitHandle.WaitOne();
17                 num++;
18                 Console.WriteLine("EventDom_" + num.ToString());
19             }
20         }
21         public void StartThread()
22         {
23             _WaitHandle.Set();
24             Console.WriteLine("EventDom->StartThread");
25         }
26         public void StopThread()
27         {
28             _WaitHandle.Reset();
29             Console.WriteLine("EventDom->StopThread");
30         }
31         public void Dispose()
32         {
33             _WaitHandle.Close();
34         }
35 
36         public static void Test()
37         {
38             EventDom eventDom = new EventDom();
39             eventDom.StopThread();
40         }
41 
42      }

 調(diào)用EventDom.Test();進(jìn)行測(cè)試,結(jié)果如下圖:

.Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)

在構(gòu)造函數(shù)中我就已經(jīng)把手動(dòng)重置事件聲明為了 信號(hào)已發(fā)狀態(tài),所以在運(yùn)行的時(shí)候,while在每次循環(huán)的時(shí)候等待接收到的信號(hào)一直都是已發(fā)送狀態(tài),所以是一直在輸出,直到調(diào)用了StopThread()方法中的Reset()方法,把狀態(tài)設(shè)置為未發(fā)送狀態(tài),才使執(zhí)行暫停。

再來(lái)看一下自動(dòng)重置,修改一下上段的代碼,

 1     public class EventDom : IDisposable
 2     {
 3         AutoResetEvent _WaitHandle;
 4         public EventDom()
 5         {
 6             _WaitHandle = new AutoResetEvent(true);
 7 
 8             Thread thread = new Thread(DoWork);
 9             thread.Start();
10         }
11         private void DoWork()
12         {
13             int num = 0;
14             while (true)
15             {
16                 _WaitHandle.WaitOne();
17                 num++;
18                 Console.WriteLine("EventDom_" + num.ToString());
19             }
20         }
21         public void StartThread()
22         {
23             _WaitHandle.Set();
24             Console.WriteLine("EventDom->StartThread");
25         }
26         public void StopThread()
27         {
28             _WaitHandle.Reset();
29             Console.WriteLine("EventDom->StopThread");
30         }
31         public void Dispose()
32         {
33             _WaitHandle.Close();
34         }
35 
36         public static void Test()
37         {
38             EventDom eventDom = new EventDom();
39             eventDom.StartThread();
40         }
41     }

首先把手動(dòng)重置類(lèi)型換成了自動(dòng)重置類(lèi)型,然后再測(cè)試代碼中把設(shè)置狀態(tài)為未發(fā)送的方法,改成了設(shè)置狀態(tài)為已發(fā)送的方法。

.Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)

這個(gè)結(jié)果是正確,因?yàn)樽詣?dòng)重置類(lèi)型就是事件狀態(tài)被設(shè)置為信號(hào)已發(fā),它就會(huì)保持這個(gè)狀態(tài),直到某個(gè)線(xiàn)程從等待調(diào)用中釋放出來(lái),然后在這個(gè)時(shí)候,它的狀態(tài)會(huì)發(fā)生改變,自動(dòng)的反轉(zhuǎn)到未發(fā)送狀態(tài)。

還有一些擴(kuò)展的知識(shí)點(diǎn)就不在這一一闡述了,希望本篇能對(duì)大家有所幫助。END

 

 

 

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI