2010-03-10 64 views
7

我來到這個防空火炮的邁克灰「關懷和單身的餵養」,是一點點被他的評價puzzeled:邁克灰辛格爾頓:配售@synchronized

此代碼是有點慢,但。 取鎖有點貴。 讓它更痛苦的是事實上絕大多數時候,鎖定是毫無意義的。當foo爲零時鎖只有 ,其中 基本上只發生一次。 單身人初始化後,需要 的鎖不見了,但鎖本身 依然存在。

+(id)sharedFoo { 
    static Foo *foo = nil; 
    @synchronized([Foo class]) { 
     if(!foo) foo = [[self alloc] init]; 
    } 
    return foo; 
} 

我的問題是,有無疑是一個很好的理由,但你爲什麼不能寫(見下文),以限制鎖時,foo是零?

+(id)sharedFoo { 
    static Foo *foo = nil; 
    if(!foo) { 
     @synchronized([Foo class]) { 
      foo = [[self alloc] init]; 
     } 
    } 
    return foo; 
} 

歡呼加里

+1

啊好了,基本上你需要的@synchronize塊內的支票? – fuzzygoat 2010-03-10 17:16:33

+0

這就是@synchronized的全部要點:一次只允許一個線程進行檢查。 – 2010-03-10 17:17:54

+0

嘗試使用dispatch_once()代替:http://stackoverflow.com/q/5720029/290295 – ctpenrose 2011-12-16 20:41:31

回答

18

因爲則測試是受爭用條件。兩個不同的線程可能會獨立測試foonil,然後(按順序)創建單獨的實例。如果一個線程執行測試,而另一個線程仍在+[Foo alloc]-[Foo init]內,但尚未設置foo,則可能會在修改後的版本中發生此問題。

順便說一下,我不會這樣做。查看dispatch_once()函數,該函數可以保證塊只在應用程序的整個生命週期中執行一次(假設您在目標平臺上有GCD)。

+0

這當然是對的。但是最好的解決方案不是最好的解決方案是兩次測試(在@ @ synchronized之外的**和**內)。那麼就沒有競賽條件和性能損失。 – 2010-03-10 17:09:18

+1

@尼克萊:告訴我,在你運行鯊魚之後會有一個表現的懲罰。 :-) – 2010-03-10 17:10:57

+0

@Graham:毫無疑問,在原始版本中性能不佳,總是需要鎖定。我在我的代碼*中擁有它,並且我運行過Shark *;)。另外,Mike Ash在他原來的博客文章中指出了這一點。 – 2010-03-10 17:16:17

1

在您的版本中,!foo的檢查可能同時發生在多個線程上,允許兩個線程跳轉到alloc塊中,其中一個在分配另一個實例之前等待另一個線程完成。

1

如果foo == nil,您可以僅通過鎖定進行優化,但之後需要再次測試(在@synchronized內)以防止競爭條件。

+ (id)sharedFoo { 
    static Foo *foo = nil; 
    if(!foo) { 
     @synchronized([Foo class]) { 
      if (!foo) // test again, in case 2 threads doing this at once 
       foo = [[self alloc] init]; 
     } 
    } 
    return foo; 
} 
+2

請參閱@mfazekas回答爲什麼這是錯誤的。 – 2010-03-11 13:10:09

7

這被稱爲double checked locking "optimization"。隨處可見,這是不安全的。即使它沒有被編譯器優化所擊敗,它也會被內存在現代機器上的工作方式所打敗,除非你使用某種圍欄/障礙。

Mike Ash also shows使用volatileOSMemoryBarrier();的正確解決方案。

問題是,當一個線程執行foo = [[self alloc] init];時,不能保證當其他線程看到foo != 0所有由init執行的內存寫入也是可見的。

另請參閱DCL and C++DCL and java瞭解更多詳情。

+0

+1感謝您的澄清。指令重新排序和無序內存訪問都是大多數程序員都不知道的概念。 – 2010-03-11 13:12:57

+3

dispatch_once是真正的解決方案,只是使用它,並退出黑客 – slf 2011-08-31 17:07:59

+0

我認爲slf有它。 http://stackoverflow.com/q/5720029/290295 – ctpenrose 2011-12-16 20:40:50

1

的最佳方式,如果你有盛大cenral調度

+ (MySingleton*) instance { 
static dispatch_once_t _singletonPredicate; 
static MySingleton *_singleton = nil; 

dispatch_once(&_singletonPredicate, ^{ 
    _singleton = [[super allocWithZone:nil] init]; 
}); 

return _singleton 
} 
+ (id) allocWithZone:(NSZone *)zone { 
    return [self instance]; 
}