2009-08-27 63 views
17

我正在編寫一個C#應用程序。 我有(一種)日誌類。並且這個日誌記錄類將被許多線程使用。 如何使這個類線程安全? 我應該讓它成爲單身? 那裏有哪些最佳實踐? 是否有我可以閱讀的關於如何使其線程安全的文檔?如何製作線程安全

感謝

+7

的Singleton模式並不意味着線程安全。 – dtb 2009-08-27 22:24:47

+2

首先仔細定義「線程安全」的含義。人們使用這個術語就像它意味着某種特定的事物,事實上,它只是意味着「在場景X中正常工作」。如果沒有「正確」的規範和X是什麼的陳述,你實際上不能實現某些東西,並且知道你解決了一個真正存在的問題。 – 2009-08-27 22:47:12

+1

請查看Joseph Albahari的[這篇文章](http://www.albahari.com/threading/part2.aspx#_ThreadSafety)。大約一半的方式進入文章是保持第二行記上「線程安全」 – 2013-01-04 06:48:05

回答

15

在C#中,任何對象都可以用來保護「關鍵部分」,換句話說,不能由兩個線程同時執行的代碼。

例如,以下內容將同步對SharedLogger.Write方法的訪問,因此在任何給定時間只有一個線程正在記錄消息。

public class SharedLogger : ILogger 
{ 
    public static SharedLogger Instance = new SharedLogger(); 

    public void Write(string s) 
    { 
     lock (_lock) 
     { 
     _writer.Write(s); 
     } 
    } 

    private SharedLogger() 
    { 
     _writer = new LogWriter(); 
    } 

    private object _lock; 
    private LogWriter _writer; 
} 
+0

就是一個很好的例子。 – Maciek 2009-08-27 22:28:28

+0

這很好,但你可以讓_lock和_writer成爲靜態而不必處理使這個事情成爲單例。但使用現有的記錄器仍然會更容易。 – 2009-08-27 22:29:37

+3

我同意使用現有的記錄器實現,但如果他在處理多線程代碼,他最終將需要知道如何正確同步訪問各種資源,而這個同樣的過程可以在其他應用... – jeremyalan 2009-08-27 22:32:57

6
  • 嘗試多個線程將不使用記錄和使用本地變量做大部分的計算,然後改變對象的狀態一個快速lock ed塊。
  • 請記住,一些變量可能會在您閱讀它們和改變狀態之間發生變化。
+0

+1很大的部分,我浪費了2周理解是 – 2013-06-12 14:00:18

9

我會使用現成的記錄儀,因爲有幾個堅如磐石,使用簡單。無需推出自己的。我建議Log4Net.

+0

log4net的是偉大的,我已經使用了多次,並一直與快樂它。 – 2009-08-27 22:30:10

+10

我同意這種記錄。但它沒有回答這個問題。 – 2009-08-27 22:44:00

+0

Log4Net是shiznit!上面的代碼是 – Crackerjack 2012-04-06 22:40:16

2

根據BCS」的答案:

BCS被描述無國籍物體的情況下。這樣一個對象本質上是線程安全的,因爲它沒有它自己的變量,可以被來自不同的角色的調用破壞。

描述的記錄器確實有一個文件句柄(對不起,不是C#用戶,也許它被稱爲IDiskFileResource或一些這樣的MS-ISM),它必須序列化使用。

因此,將消息的存儲與將它們寫入日誌文件的邏輯分開。邏輯一次只能處理一條消息。

其中一種方法是:如果記錄器對象要保留消息對象的隊列,並且記錄器對象僅具有從隊列中彈出消息的邏輯,則從消息對象中提取有用的內容,然後將它寫入日誌,然後在隊列中查找另一條消息 - 然後通過讓隊列的add/remove/queue_size/etc操作線程安全,可以使該線程安全。它需要logger類,一個消息類和一個線程安全隊列(可能是第三個類,它的一個實例是logger類的成員變量)。

+0

另一種選擇(在某些情況下)是讓記錄器調用在本地存儲器中生成完整記錄,然後在一次調用中將其寫入輸出流。 IIRC大多數操作系統都提供了一個系統調用,它可以在任何情況下(幾乎?)對流進行原子寫入,但資源耗盡,然後還有其他問題。 – BCS 2010-11-15 21:22:10

8

我不知道我可以添加任何東西,已經說過關於使一個日誌類線程安全。如前所述,爲此,您必須同步對資源(即日誌文件)的訪問,以便一次只有一個線程嘗試登錄。 C#lock關鍵字是執行此操作的正確方法。然而,我會解決(1)單例方法和(2)最終決定使用的方法的可用性。(1)如果您的應用程序將其所有日誌消息寫入單個日誌文件,那麼單例模式絕對是要走的路線。日誌文件將在啓動時打開並在關閉時關閉,單例​​模式完全符合這一操作概念。正如@dtb指出的,但請記住,讓一個類成爲單例並不能保證線程安全。使用lock關鍵字。

(2)對於該方法的實用性,考慮這個建議的解決方案:

public class SharedLogger : ILogger 
{ 
    public static SharedLogger Instance = new SharedLogger(); 
    public void Write(string s) 
    { 
     lock (_lock) 
     { 
     _writer.Write(s); 
     } 
    } 
    private SharedLogger() 
    { 
     _writer = new LogWriter(); 
    } 
    private object _lock; 
    private LogWriter _writer; 
} 

讓我先說,這種方法一般是OK。它通過Instance靜態變量定義了一個單例實例SharedLogger,並通過私有構造函數阻止其他實例化類。這是單身模式的本質,但我強烈建議閱讀並遵循Jon Skeet關於singletons in C#的建議,然後再進一步處理。

但是,我想要關注的是該解決方案的可用性。通過'可用性',我指的是用這個實現來記錄消息的方式。考慮調用的樣子:

SharedLogger.Instance.Write("log message"); 

整個'實例'部分看起來不正確,但沒有辦法避免它給出實現。相反,考慮這個選擇:

public static class SharedLogger : ILogger 
{ 
    private static LogWriter _writer = new LogWriter(); 
    private static object _lock = new object(); 
    public static void Write(string s) 
    { 
     lock (_lock) 
     { 
      _writer.Write(s); 
     } 
    } 
} 

注意,類現在靜態的,這意味着它的所有成員和方法必須是靜態的。它與前面的例子沒有本質區別,但考慮它的用法。

SharedLogger.Write("log message"); 

這對代碼來說要簡單得多。

重點不是詆譭前一種解決方案,而是暗示您選擇的任何解決方案的可用性都是一個不容忽視的重要方面。一個好的,可用的API可以使代碼更容易編寫,更優雅,更易於維護。

+0

我同意這一點......更不用說廢話了。 – 2012-01-19 01:32:44

+0

儘可能使用靜態類作爲經驗法則。 – 2012-01-19 01:33:03

+0

我們不能只用lock(this)而不是lock(_this)? (使用即時作爲鎖定對象?) – 2013-06-12 14:07:43

1

在我看來,上面提供的代碼不再是線程安全的: 在以前的解決方案中,您必須爲每個對象創建一個SharedLogger的新對象和Write方法。

現在你只需要一個Write方法,用於所有線程,例如:

線程1: SharedLogger.Write( 「線程1」)

線程2: SharedLogger。寫(「線程2」);

public static void Write(string s) 
    { 
     // thread 1 is interrupted here <= 
     lock (_lock) 
     { 
      _writer.Write(s); 
     } 
    } 
  • 線程1想要寫的消息,而是由線程2(參見評論)
  • 螺紋2個覆蓋線程1的消息中斷,並且通過螺紋中斷1

  • 線程1獲取鎖並寫入「線程2」

  • 線程1釋放鎖
  • 線程2獲取鎖並寫入「線程2"
  • 線程2釋放鎖

糾正我,當我錯了......

0

如果性能」的主要問題,例如,如果該類不是下了不少負載的,只是這樣做:

讓你的類繼承ContextBoundObject

這個屬性應用到你的類[同步]

您的整個班級現在只能一次訪問一個線程。

對於診斷來說它確實更加有用,因爲速度方面它幾乎是最糟糕的情況......但要快速確定「這是一個奇怪的問題是一個賽車狀況」,將其拋出,重新運行測試..如果問題出現了......你知道這是一個線程的問題...

更高性能的選擇是讓你的日誌類有一個線程安全的消息隊列(接受日誌消息,然後才拉出來,並依次對其進行處理.. 。

例如,在新的平行的東西ConcurrentQueue類是一個很好的線程安全的隊列。

或用戶log4net RollingLogFileAppender,它已經是thread safe