2010-03-10 9 views
7

tôi đến accross này trên Mike Ash "Chăm sóc và nuôi dưỡng độc thân" và là một chút puzzeled bởi lời nhận xét của mình:Mike Ash Singleton: Đổ @synchronized

Mã này là loại chậm, mặc dù. Lấy khóa có phần đắt tiền. Làm cho nó đau đớn hơn là sự kiện rằng phần lớn thời gian, khóa là vô nghĩa. Khóa là chỉ cần thiết khi foo là không, trong đó về cơ bản chỉ xảy ra một lần. Sau khi singlet được khởi tạo, cần có khóa đã biến mất, nhưng bản thân khóa vẫn còn.

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

Câu hỏi của tôi là, và không có nghi ngờ một lý do chính đáng cho điều này nhưng tại sao bạn không thể viết (xem bên dưới) để hạn chế khóa để khi foo là con số không?

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

cổ vũ gary

+1

Ah ok, vì vậy về cơ bản bạn cần kiểm tra bên trong khối @synchronize? – fuzzygoat

+0

Đó là toàn bộ điểm của @synchronized: Cho phép một luồng tại một thời điểm để thực hiện kiểm tra. –

+0

Hãy thử dispatch_once() để thay thế: http://stackoverflow.com/q/5720029/290295 – ctpenrose

Trả lời

18

Bởi vì sau đó kiểm tra là đối tượng của một tình trạng chủng tộc. Hai luồng khác nhau có thể kiểm tra độc lập rằng foonil và sau đó (tuần tự) tạo các phiên bản riêng biệt. Điều này có thể xảy ra trong phiên bản đã sửa đổi của bạn khi một chuỗi thực hiện kiểm tra trong khi một chuỗi khác vẫn nằm trong số +[Foo alloc] hoặc -[Foo init], nhưng chưa đặt foo.

Nhân tiện, tôi sẽ không làm theo cách đó. Kiểm tra các chức năng dispatch_once(), cho phép bạn đảm bảo rằng một khối chỉ được thực hiện một lần trong suốt thời gian của ứng dụng của bạn (giả sử bạn có GCD trên nền tảng bạn đang nhắm mục tiêu).

+0

Điều đó tất nhiên là đúng. Nhưng sẽ không phải là giải pháp tốt nhất để kiểm tra hai lần (bên trong ** và ** bên ngoài '@ synchronized'). Sau đó, sẽ không có điều kiện chủng tộc hay hình phạt thi đấu. –

+1

@Nikolai: cho tôi biết có một hình phạt hiệu suất _after_ bạn đã chạy Shark. :-) –

+0

@Graham: Không có nghi ngờ rằng hiệu suất là xấu trong phiên bản gốc luôn luôn có khóa. Tôi đã có nó trong mã của tôi * và tôi đã chạy Shark *;). Ngoài ra, Mike Ash đã chỉ ra nó trong bài đăng blog gốc của anh ấy. –

1

Trong phiên bản, séc !foo có thể xảy ra trên nhiều luồng cùng một lúc, cho phép hai luồng nhảy vào khối alloc, một lần chờ người khác hoàn thành trước khi cấp phát một phiên bản khác.

1

Bạn có thể tối ưu hóa bằng cách chỉ lấy khóa nếu foo == nil, nhưng sau đó bạn cần kiểm tra lại (trong @synchronized) để bảo vệ khỏi điều kiện chủng tộc.

+ (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

Xem @mfazekas câu trả lời vì sao điều này sai. –

7

Đây được gọi là double checked locking "optimization". Như được ghi chép ở khắp mọi nơi, điều này không an toàn. Ngay cả khi nó không bị đánh bại bởi một trình tối ưu hóa trình biên dịch, nó sẽ bị đánh bại cách bộ nhớ hoạt động trên các máy hiện đại, trừ khi bạn sử dụng một số loại hàng rào/rào cản.

Mike Ash also shows giải pháp đúng bằng cách sử dụng volatileOSMemoryBarrier();.

Vấn đề là khi một chuỗi thực hiện foo = [[self alloc] init];, không có gì đảm bảo rằng khi một chuỗi khác xem foo != 0, tất cả ghi bộ nhớ được thực hiện bởi init cũng hiển thị.

Đồng thời xem DCL and C++DCL and java để biết thêm chi tiết.

+0

+1 Cảm ơn bạn đã làm rõ điều này. Hướng dẫn sắp xếp lại và truy cập bộ nhớ ngoài trật tự là cả hai khái niệm mà hầu hết các lập trình viên không biết. –

+3

dispatch_once là giải pháp thực sự, chỉ cần sử dụng và bỏ hack – slf

+0

Tôi nghĩ rằng slf có nó. http://stackoverflow.com/q/5720029/290295 – ctpenrose

1

Cách tốt nhất nếu bạn có lớn cenral văn

+ (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]; 
}