2009-09-23 93 views
2

最底層的類是快速,線程安全,「無鎖定」,懶惰初始化程序的實現。線程安全的懶惰初始化程序;交換Func <>是個好主意嗎?

我說「鎖定自由」,雖然它不完全;它在初始化之前使用一個鎖,然後替換該調用以獲取不使用該鎖的數據。 (類似於使用volatile成員的雙重檢查鎖;並且從性能測試中我得到v類似的結果。)

我的問題是;這是一件好事/安全的事情嗎?

(我寫了這個之後,我注意到,.NET 4.0有一個LazyInit<T>執行相同的操作多,但在我創建了一個實現非常高的人爲的例子是稍快:-))

NB類已被修改爲包括Value成員和揮發性的_get

class ThreadSafeInitializer<TOutput> 
    where TOutput : class 
{ 
    readonly object _sync = new object(); 
    TOutput _output; 
    private volatile Func<TOutput> _get; 

    public TOutput Value { get { return _get(); } } 

    public ThreadSafeInitializer(Func<TOutput> create) 
    { 
     _get =() => 
     { 
      lock (_sync) 
      { 
       if (_output == null) 
       { 
        _output = create(); 
        _get =() => _output; // replace the Get method, so no longer need to lock 
       } 
       return _output; 
      } 
     }; 
    } 
} 
+0

+1,什麼不該做的一個非常聰明的例子 – 2009-09-24 01:19:15

+0

嗯,我認爲這有點苛刻;儘管正如我承認的,從鎖中調用未知的代碼是一件壞事,但正如Sam在下面注意到的那樣,.NET 4.0框架附帶的是什麼...... – 2009-09-24 20:51:02

回答

1

充分利用Get成員的多變,讓你一定要調用當前版本。就我所知,這種修改是安全的。

+0

您是否需要使其變得易變?如果你有替換的Get或舊的(它只是有鎖定命中)並不重要。隨着時間的推移,讓它通過任何緩存刷新......? – 2009-09-24 00:43:30

+0

對於非易失性字段,對指令進行重新排序的優化技術可能導致多線程程序出現意外和不可預知的結果,這些程序訪問字段時沒有同步,如鎖定語句提供的那樣(第8.12節) – 2009-09-24 00:47:41

+0

Get被鎖訪問聲明...... – 2009-09-24 00:48:13

2

正如Guffa說,你要添加的揮發性物質在獲取,使這個安全

但是我發現執行情況有點棘手跟隨。我認爲它爲了自己的利益而試圖有點太過幻想。

所以東西沿着這些路線更容易遵循:

class Lazy<T> { 

    readonly object sync = new object(); 
    Func<T> init; 
    T result; 
    volatile bool initialized; 

    public Lazy(Func<T> func) { 
     this.init = func; 
    } 

    public T Value { 
     get { 

      if(initialized) return result; 

      lock (sync) { 
       if (!initialized) { 
        result = this.init(); 
        initialized = true; 
       } 
      } 
      return result; 
     } 
    } 
} 

次要優點:

  • 少花哨
  • 調用函數功能
  • 鎖的性能沒有隻獲取了初始化。通過obj.Value
  • 訪問值比obj.Get更加直觀一點()
+0

順便說一下,.NET 4.0中包含'懶惰的'結構。 – 2009-09-24 00:12:36

+0

但是,隨着功能世界的成熟,它真的是「幻想」還是僅僅與人們習慣的有點不同? (從性能的角度來看,你的表現會更糟糕(在我設計的例子中)) – 2009-09-24 00:53:24

+0

+1爲了可讀性,但請參閱下面關於整個想法的回覆。 – 2009-09-24 01:52:28

2

我寫這篇文章的代碼很多:

private SomeThing _myThing = null; 
public SomeThing MyThing 
{ get { return _myThing != null ? _myThing : _myThing = GetSomeThing(); } } 

延遲加載得很好,可讀性很強。我的觀點?缺少鎖(對象)不是偶然的。使用鎖延遲加載屬性會要求麻煩國際海事組織。

鎖定語句的使用應該嚴格控制,保羅和山姆在此指責他們在鎖定時調用他們沒有寫入的代碼。也許這沒什麼大不了的,也許它會讓你的程序陷入僵局。而你從一個無辜的財產內部做到這一點,讓你可能節省幾毫秒?

我的猜測是,如果你可以安全地加載它兩次沒有災難性的結果,會更好。然後在罕見的情況下,兩個線程同時訪問相同的屬性,然後加載兩個實例...取決於你的加載,如果這真的是一件壞事,我敢打賭,大多數時候,每個線程得到並不重要一個不同的實例。如果確實如此,我會建議在施工時執行所需的鎖定,不要擔心延遲加載。因爲構造函數只能在一個線程上發生,所以根本不需要鎖。

+0

+ 1指責羞恥地承認:-) – 2009-09-24 01:59:29

+0

+1爲務實,明智的做法。 :) – 2009-09-24 02:02:22

+0

如果您想模擬框架中發佈的LazyInitMode.EnsureSingleExecution(v4.0),鎖定是不可避免的。如果您對AllowMultipleExecution感到滿意,那就去吧。它不是關於保存時間,如果同時調用一些初始化代碼可能會導致更大的問題 – 2009-09-24 02:52:36