多線程對共享資源的訪問


一.什么是多線程? 

線程是程序中一個單一的順序控制流程.在單個程序中同時運行多個線程完成不同的工作,稱為多線程.

所有的線程雖然在微觀上是串行執行的,但是在宏觀上你完全可以認為它們在並行執行

二.那什么是線程呢?

線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。

這里的代碼區共享和共同執行同樣的函數就是我們多線程處理的關鍵!!

Thread類有幾個至關重要的方法,描述如下:

Start():啟動線程;

Sleep(int):靜態方法,暫停當前線程指定的毫秒數;

Abort():通常使用該方法來終止一個線程;

Suspend():該方法並不終止未完成的線程,它僅僅掛起線程,以后還可恢復;

Resume():恢復被Suspend()方法掛起的線程的執行;

但是在.net framework中一些線程的使用方法resume(),Suspend(),已經過時

resume():繼續已掛起的線程。

Suspend():掛起線程,或者如果線程已掛起,則不起作用

我在想是不是這些方法只是封裝起來了,或者是由於這些方法容易導致程序錯誤

是不是但我們調用waitOne()方法的時候,當前線程就被掛起了呢?

而當我們使用EventWaitHandle.Set 方法時會resume()原先掛起的線程?

在前面線程的定義中提到了:"線程的代碼區是共享的,即不同的線程可以執行同樣的函數。"

這就帶來了一個麻煩,那就是當多個線程同時訪問該代碼段,改變代碼段上的數據時,我們無法確切的知道數據的准確結果,因為我們無法知道某一時有哪幾個線程訪問了代碼段,修改了數據,在編程的過程中這種不確定性必須消除的!! 

三.如何來消除共享代碼段帶來的數據不確定性呢?

  微軟提供了一下幾種方法:

獨占鎖(Lock):鎖定的最簡單的形式是 C# 的 lock 語句,該語句可控制對代碼塊的訪問。

Monitor 類:使用 Enter 和 Exit 方法標記臨界區的開頭和結尾。而lock 關鍵字在塊的開始處調用 Enter,而在塊的結尾處調用 Exit。 所以從本質上來講Lock就是對Monitor的封裝。只不過Lock比Monitor看上去要簡潔一些。

其中Monitor類中還包括了兩個類分別是;pulse()和Wait()方法;這各方法可以很方便的對線程狀態Running運行,WaitSleepJoin封鎖進行控制。

有兩個方法要提一下,有時候線程的實現就靠這兩個了!!

Thread.Join 方法 :阻塞調用線程,直到某個線程終止時為止。

Monitor.Pulse 方法:通知等待隊列中的線程鎖定對象狀態的更改。也就是在接收到脈沖后,等待線程就被移動到就緒隊列中。在調用 Pulse 的線程釋放鎖后,就緒隊列中的下一個線程(不一定是接收到脈沖的線程)將獲得該鎖。

上面我理解的是如果對代碼段中的數據的訪問,是為了避免多線程訪問時,數據出現不確定性,出現數據庫中所謂的臟數據。

下面我理解的是線程之間的執行順序的問題,換句話說就是根據事情的先后次序來處理Thread。貌似就好比一條流水線。

多線程處理的時候,我們通常是同時開啟多個要處理的線程,如果是按事情的先后順序來處理線程的話,當某個線程運行到某處,必須接受另外一個線程的結果才能繼續運行,那么這個時候線程必須封鎖,處於等待狀態。

我們的主角出來了,那就WaitHandle 類,封裝等待對共享資源的獨占訪問的操作系統特定的對象。

四.WaitHandle 類:

下面來簡單介紹一下這個類:既然是用來封鎖等待線程的,那么不可避免的會有wait方法:WaitOne(),WaitAny(WaitHandle[]) ,WaitAll(WaitHandle[]) ,同時當某一線程等來了需要的數據時,我們還要喚醒這個線程,要發一個信號給這個線程,"數據已經准備好了,可以繼續運行了"

所以Waithandle類提供了SignalAndWait(WaitHandle, WaitHandle):向一個 WaitHandle 發出信號並等待另一個。

微軟為了方面開發人員使用,還對WaitHandle類進行了擴展繼承,下面來看看有哪些繼承類  

1.System.Threading.EventWaitHandle :表示一個線程同步事件。 

EventWaitHandle 類允許線程通過發信號互相通信。

通常,一個或多個線程在 EventWaitHandle 上阻止,直到一個未阻止的線程調用 Set 方法,以釋放一個或多個被阻止的線程。

它又包括一下兩個繼承類:

    1. System.Threading.AutoResetEvent 自動重置事件提供對資源的獨占訪問。

      如果在沒有線程等待時發出自動重置事件信號,則該信號將一直保留到有一個線程嘗試在其上等待為止。

      然后,該事件釋放線程並立即重置,這會阻止后面的線程。

    2. System.Threading.ManualResetEvent     手動重置事件類似於入口。

      當事件不處於終止狀態時,在該事件上等待的線程將阻止。

      當事件處於終止狀態時,所有等待的線程都被釋放,而事件一直保持終止狀態(即后面的等待不阻止),直到它的 Reset 方法被調用。

      如果一個線程必須完成一項活動后,其他線程才能繼續,則手動重置事件很有用。

下面是我對EventWaithandle的理解:

首先主線程初始化一個EventWaitHandle的類實例,private static EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);

它可以有兩種模式,AutoReset和ManualReset,我們從字面上發現兩個模式都帶有一個相同的字段Reset,

我們聯想一下EventHandle類中是不是有個方法叫做Reset: 將事件狀態設置為非終止狀態,導致線程阻止。那么進一步猜想,是不是AutoReset模式就是自動調用了Reset()方法,也就是在自動事件狀態設置為非終止狀態呢?

這是官方說明:

調用 Set 向 AutoResetEvent 發信號以釋放等待線程。

AutoResetEvent 將保持終止狀態,直到一個正在等待的線程被釋放,然后自動返回非終止狀態。 如果沒有任何線程在等待,則狀態將無限期地保持為終止狀態。

 

說明我們的猜想是正確的!!!那前面我們所說的事件又是什么呢?初始化一個EventWaitHandle實例又是什么意思呢?與我們所講到的多線程又有什么關聯呢?我理解的前面提到的事件也就是一個線程,而初始化一個EventWaitHandle實例,也就是初始化一個線程,只不過這個線程只做一件事就是"卡位","堵口子"。強制其他線程等待,只有當這個事件處於終止狀態的時候,也就是調用set(),其他處於等待線程才能得到釋放,才可以運行,而當這個事件處於非終止狀態的時候,也就是調用Reset()時,其他線程調用WaitOne就會使線程處於等待狀態。也就是說其實EventWaitHandle,就是利用一個線程不斷的搶占和釋放資源,以便控制線程的執行順序,達到多線程先后運行的目的!!System.Threading.Mutex:一個同步基元,也可用於進程間同步。 

  當兩個或更多線程需要同時訪問一個共享資源時,系統需要使用同步機制來確保一次只有一個線程使用該資源。    

2.System.Threading.Mutex:一個同步基元,也可用於進程間同步。 

當兩個或更多線程需要同時訪問一個共享資源時,系統需要使用同步機制來確保一次只有一個線程使用該資源。

Mutex 是同步基元,它只向一個線程授予對共享資源的獨占訪問權。

如果一個線程獲取了互斥體,則要獲取該互斥體的第二個線程將被掛起,直到第一個線程釋放該互斥體。

這與前面我解釋的原因是一樣的,就是利用一個線程控制其他線程的ThreadState狀態,使線程的狀態處於運行Running或是封鎖WaitSleepJoin狀態! 

3.System.Threading.Semaphore:限制可同時訪問某一資源或資源池的線程數。

這種處理是對前面的方法的進一步處理,就好比.net中的委托delegate和事件委托Event delegate,事件委托中可以多播委托。也就是構造了一個數組存儲多個委托。並且可以對委托數組進行-=和+=。

這里的信號量也有類型的封裝,信號量中存在一個數組Threadpool線程池,其實是一個隊列。用來存放線程。當一個線程結束或是線程池為空時,其他處於waitOne中的線程就可以進入線程池,改變處於Wait狀態TreadState的線程為Running。而運行完的線程就會從隊列的頭部出去。后來的進程從尾部進去。

總結一下: 繼承WaitHandle類的幾個類所作的事情無非是在改變線程的狀態,來控制線程的執行順序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM