簡體   English   中英

為什么 Mutex 在處置時沒有被釋放?

[英]Why doesn't Mutex get released when disposed?

我有以下代碼:

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that deals with a specific TCP port
       // Don't want this to run at the same time in another process
    }
}

我在if塊中設置了一個斷點,並在 Visual Studio 的另一個實例中運行了相同的代碼。 正如預期的那樣, .WaitOne調用阻塞。 然而,令我驚訝的是,只要我在第一個實例中繼續並且using塊終止,我就會在第二個進程中收到一個關於廢棄互斥鎖的異常。

修復方法是調用ReleaseMutex

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that deals with a specific TCP port
       // Don't want this to run twice in multiple processes
    }

    mut.ReleaseMutex();
}

現在,事情按預期進行。

我的問題:通常IDisposable是它會清除您放入的任何狀態。我可以看到在using塊中可能有多個等待釋放,但是當 Mutex 的句柄被釋放時,它不應該被釋放自動地? 換句話說,如果我在using塊中,為什么我需要調用ReleaseMutex

我現在還擔心如果if塊中的代碼崩潰,我會放棄周圍的互斥鎖。

Mutex放入using塊有什么好處嗎? 或者,我應該新建一個Mutex實例,將其包裝在 try/catch 中,然后在finally塊中調用ReleaseMutex() (基本上完全實現我認為Dispose()會做的事情)

文檔解釋(“備注”一節),有實例化一個互斥對象之間和概念上的差異(,其實,做什么特別的盡可能同步雲)獲取互斥(使用WaitOne )。 注意:

  • WaitOne返回一個布爾值,這意味着獲取互斥量可能會失敗(超時),兩種情況都必須處理
  • WaitOne返回true ,則說明調用線程已經獲取了 Mutex 並且必須調用ReleaseMutex ,否則 Mutex 將被放棄
  • 當它返回false ,調用線程不得調用ReleaseMutex

因此,互斥量不僅僅是實例化。 至於你是否應該使用using ,讓我們來看看Dispose做了什么( 繼承自WaitHandle ):

protected virtual void Dispose(bool explicitDisposing)
{
    if (this.safeWaitHandle != null)
    {
        this.safeWaitHandle.Close();
    }
}

正如我們所看到的,互斥鎖沒有被釋放,但涉及到一些清理工作,所以堅持using將是一個很好的方法。

至於您應該如何進行,您當然可以使用try/finally塊來確保,如果獲取了互斥鎖,它會被正確釋放。 這可能是最直接的方法。

如果您真的不關心無法獲取 Mutex 的情況(您沒有指出,因為您將TimeSpan傳遞給WaitOne ),您可以將Mutex包裝在您自己的實現IDisposable的類中,在構造函數(使用不帶參數的WaitOne() ),並在Dispose釋放它。 雖然,我可能不建議這樣做,因為如果出現問題,這會導致您的線程無限期地等待,並且不管有充分的理由在嘗試獲取時顯式處理這兩種情況,如@HansPassant 所述。

這個設計決定是很久很久以前做出的。 21 多年前,遠在 .NET 被設想或 IDisposable 的語義被考慮之前。 .NET Mutex 類是底層操作系統對互斥鎖的支持的包裝類。 構造函數調用CreateMutex , WaitOne() 方法調用WaitForSingleObject()

請注意 WaitForSingleObject() 的 WAIT_ABANDONED 返回值,它是生成異常的值。

Windows 設計者制定了嚴格的規則,即擁有互斥鎖的線程必須在退出之前調用 ReleaseMutex()。 如果不是這樣,這是一個非常強烈的跡象,表明線程以意外的方式終止,通常是通過異常。 這意味着同步丟失,這是一個非常嚴重的線程錯誤。 與 Thread.Abort() 相比,出於同樣的原因,這是在 .NET 中終止線程的一種非常危險的方式。

.NET 設計者沒有以任何方式改變這種行為。 至少不是因為除了執行等待之外,沒有任何方法可以測試互斥鎖的狀態。 必須調用 ReleaseMutex()。 請注意,您的第二個片段也不正確; 你不能在你沒有獲得的互斥鎖上調用它。 它必須移動到 if() 語句主體內。

好的,發布我自己的問題的答案。 據我所知,是實現Mutex的理想方式:

  1. 總是被處置
  2. 僅當WaitOne成功WaitOne被釋放。
  3. 如果任何代碼拋出異常,都不會被放棄。

希望這可以幫助某人!

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
        try
        {
           // Some code that deals with a specific TCP port
           // Don't want this to run twice in multiple processes        
        }
        catch(Exception)
        {
           // Handle exceptions and clean up state
        }
        finally
        {
            mut.ReleaseMutex();
        }
    }
}

更新:也許有人會說,如果內的代碼try塊把你的資源處於不穩定的狀態,你應該釋放互斥鎖,而是讓它拋棄。 換句話說,只需調用mut.ReleaseMutex(); 當代碼成功完成時,不要把它放在finally塊中。 獲取 Mutex 的代碼然后可以捕獲此異常並執行正確的操作

在我的情況下,我並沒有真正改變任何狀態。 我暫時使用了一個 TCP 端口,不能同時運行另一個程序實例。 出於這個原因,我認為我上面的解決方案很好,但你的可能不同。

互斥鎖的主要用途之一是確保唯一看到共享對象處於不滿足其不變量的狀態的代碼是(希望是暫時的)將對象置於該狀態的代碼。 需要修改對象的代碼的正常模式是:

  1. 獲取互斥鎖
  2. 對對象進行更改導致其狀態變為無效
  3. 對對象進行更改,使其狀態再次變為有效
  4. 釋放互斥鎖

如果在#2 開始之后和#3 完成之前出現問題,對象可能會處於不滿足其不變量的狀態。 由於正確的模式是在處置互斥鎖之前先釋放它,因此代碼在不釋放互斥鎖的情況下處置它的事實意味着某處出了問題。 因此,代碼進入互斥鎖可能不安全(因為它還沒有被釋放),但沒有理由等待互斥鎖被釋放(因為 - 已經被釋放 - 它永遠不會被釋放) . 因此,正確的做法是拋出異常。

一種比 .NET mutex 對象實現的模式更好的模式是讓“acquire”方法返回一個IDisposable對象,該對象不封裝互斥鎖,而是封裝其特定的獲取。 處理該對象將釋放互斥鎖。 代碼看起來像這樣:

using(acq = myMutex.Acquire())
{
   ... stuff that examines but doesn't modify the guarded resource
   acq.EnterDanger();
   ... actions which might invalidate the guarded resource
   ... actions which make it valid again
   acq.LeaveDanger();
   ... possibly more stuff that examines but doesn't modify the resource
}

如果內部代碼在EnterDangerLeaveDanger之間失敗,則獲取對象應通過對其調用Dispose使互斥鎖無效,因為受保護的資源可能處於損壞狀態。 如果內部代碼在其他地方失敗,則應釋放互斥鎖,因為受保護的資源處於有效狀態,並且using塊中的代碼將不再需要訪問它。 對於實現該模式的庫,我沒有任何特別的建議,但作為其他類型互斥體的包裝器來實現並不是特別困難。

我們需要了解更多然后 .net 知道發生了什么 MSDN 頁面的開頭給出了某人“奇怪”正在發生的第一個提示:

也可用於進程間同步的同步原語。

Mutex 是一個Win32命名對象”,每個進程通過名稱鎖定它,.net 對象只是 Win32 調用的包裝器。 Muxtex 本身位於 Windows 內核地址空間中,而不是您的應用程序地址空間中。

在大多數情況下,最好使用Monitor ,如果您只是嘗試同步對單個進程中的對象的訪問。

如果您需要保證釋放互斥鎖,請切換到 try catch finally 塊並將互斥鎖釋放放在 finally 塊中。 假設您擁有並擁有互斥鎖的句柄。 在調用 release 之前需要包含該邏輯。

閱讀ReleaseMutex的文檔,似乎設計決定是應該有意識地釋放互斥鎖。 如果未調用ReleaseMutex ,則表示受保護部分異常退出。 將釋放放在 finally 或 dispose 中,繞過了這種機制。 當然,您仍然可以隨意忽略 AbandonedMutexException。

請注意:垃圾收集器執行的 Mutex.Dispose() 失敗,因為垃圾收集過程不擁有 Windows 的句柄。

Dispose依賴於WaitHandle被釋放。 因此,即使using調用Dispose ,它也不會生效,直到滿足穩定狀態的條件。 當您調用ReleaseMutex您是在告訴系統您正在釋放資源,因此可以自由地處理它。

對於最后一個問題。

將 Mutex 放入 using 塊有什么好處嗎? 或者,我應該新建一個 Mutex 實例,將其包裝在 try/catch 中,然后在 finally 塊中調用 ReleaseMutex() (基本上完全實現我認為 Dispose() 會做的事情)

如果不處理 mutex 對象,創建過多的 mutex 對象可能會遇到以下問題。

---> (Inner Exception #4) System.IO.IOException: Not enough storage is available to process this command. : 'ABCDEFGHIJK'
 at System.Threading.Mutex.CreateMutexCore(Boolean initiallyOwned, String name, Boolean& createdNew)
 at NormalizationService.Controllers.PhysicalChunkingController.Store(Chunk chunk, Stream bytes) in /usr/local/...

該程序使用命名的互斥鎖並在並行 for 循環中運行 200,000 次。
添加 using 語句可解決此問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM