Làm thế nào để khóa / mở khóa không đồng bộ trong Objective-C?


201

@Synyncized không sử dụng "khóa" và "mở khóa" để đạt được loại trừ lẫn nhau? Làm thế nào để nó khóa / mở khóa sau đó?

Đầu ra của chương trình sau chỉ là "Hello World".

@interface MyLock: NSLock<NSLocking>
@end

@implementation MyLock

- (id)init {
    return [super init];
}

- (void)lock {
    NSLog(@"before lock");
    [super lock];
    NSLog(@"after lock");
}

- (void)unlock {
    NSLog(@"before unlock");
    [super unlock];
    NSLog(@"after unlock");
}

@end


int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    MyLock *lock = [[MyLock new] autorelease];
    @synchronized(lock) {
        NSLog(@"Hello World");
    }

    [pool drain];
}


10
Bạn không cần ghi đè init nếu bạn không cần nó. Thời gian chạy tự động gọi triển khai của lớp cha nếu bạn không ghi đè một phương thức.
Constantino Tsarouhas

3
Một điều quan trọng cần lưu ý là đoạn mã trên không được đồng bộ hóa. Đối locktượng được tạo trên mỗi cuộc gọi, vì vậy sẽ không bao giờ xảy ra trường hợp một @synchronizedkhối khóa một khối khác. Và điều này có nghĩa là không có loại trừ lẫn nhau.) Tất nhiên, ví dụ trên đang thực hiện thao tác main, vì vậy dù sao cũng không có gì để loại trừ, nhưng người ta không nên sao chép mã đó ở nơi khác.
Licks nóng

3
Sau khi đọc trang SO này, tôi quyết định điều tra @ đồng bộ kỹ lưỡng hơn một chút và viết một bài đăng blog trên đó. Bạn có thể tìm thấy nó hữu ích: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Câu trả lời:


323

Đồng bộ hóa mức ngôn ngữ Objective-C sử dụng mutex, giống như NSLockkhông. Về mặt ngữ nghĩa có một số khác biệt kỹ thuật nhỏ, nhưng về cơ bản là chính xác khi nghĩ về chúng như hai giao diện riêng biệt được triển khai trên một thực thể chung (nguyên thủy hơn).

Đặc biệt với một NSLockbạn có một khóa rõ ràng trong khi với @synchronizedbạn có một khóa ẩn liên quan đến đối tượng bạn đang sử dụng để đồng bộ hóa. Lợi ích của việc khóa cấp độ ngôn ngữ là trình biên dịch hiểu nó để nó có thể xử lý các vấn đề phạm vi, nhưng về mặt cơ bản thì chúng hoạt động giống nhau.

Bạn có thể nghĩ về việc @synchronizedviết lại trình biên dịch:

- (NSString *)myString {
  @synchronized(self) {
    return [[myString retain] autorelease];
  }
}

được chuyển thành:

- (NSString *)myString {
  NSString *retval = nil;
  pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
  pthread_mutex_lock(self_mutex);
  retval = [[myString retain] autorelease];
  pthread_mutex_unlock(self_mutex);
  return retval;
}

Điều đó không chính xác bởi vì biến đổi thực tế phức tạp hơn và sử dụng các khóa đệ quy, nhưng nó sẽ có được điểm.


17
Bạn cũng quên việc xử lý ngoại lệ mà @synyncized thực hiện cho bạn. Và theo tôi hiểu, phần lớn việc này được xử lý trong thời gian chạy. Điều này cho phép tối ưu hóa các khóa không được bảo vệ, v.v.
Quinn Taylor

7
Như tôi đã nói, các công cụ được tạo thực tế phức tạp hơn, nhưng tôi không cảm thấy muốn viết các chỉ thị của phần để xây dựng các bảng thư giãn DWARF3 ;-)
Louis Gerbarg

Và tôi không thể đổ lỗi cho bạn. :-) Cũng lưu ý rằng OS X sử dụng định dạng Mach-O thay vì DWARF.
Quinn Taylor

5
Không ai sử dụng DWARF như một định dạng nhị phân. OS X không sử dụng DWARF cho các biểu tượng gỡ lỗi và nó sử dụng các bảng thư giãn DWARF cho các ngoại lệ chi phí bằng 0
Louis Gerbarg

7
Để tham khảo, tôi đã viết các phụ trợ trình biên dịch cho Mac OS X ;-)
Louis Gerbarg

40

Trong Objective-C, một @synchronizedkhối sẽ tự động khóa và mở khóa (cũng như các trường hợp ngoại lệ có thể) cho bạn. Thời gian chạy về cơ bản tạo ra NSRecursiveLock được liên kết với đối tượng bạn đang đồng bộ hóa. Tài liệu này của Apple giải thích chi tiết hơn. Đây là lý do tại sao bạn không thấy các thông điệp tường trình từ lớp con NSLock của bạn - đối tượng bạn đồng bộ hóa có thể là bất cứ thứ gì, không chỉ là NSLock.

Về cơ bản, @synchronized (...)là một cấu trúc thuận tiện giúp hợp lý hóa mã của bạn. Giống như hầu hết các khái niệm trừu tượng hóa, nó có liên quan đến chi phí (nghĩ về nó như một chi phí ẩn) và thật tốt khi biết điều đó, nhưng hiệu suất thô có lẽ không phải là mục tiêu tối cao khi sử dụng các cấu trúc như vậy.


1
Liên kết đó đã hết hạn. Đây là liên kết được cập nhật: developer.apple.com/l
Ariel Steiner

31

Thực ra

{
  @synchronized(self) {
    return [[myString retain] autorelease];
  }
}

biến đổi trực tiếp thành:

// needs #import <objc/objc-sync.h>
{
  objc_sync_enter(self)
    id retVal = [[myString retain] autorelease];
  objc_sync_exit(self);
  return retVal;
}

API này có sẵn từ iOS 2.0 và được nhập bằng ...

#import <objc/objc-sync.h>

Vì vậy, nó cung cấp không hỗ trợ để xử lý sạch các ngoại lệ ném?
Dustin

Đây có phải là tài liệu ở đâu đó?
jbat100

6
Có một cái nẹp không cân bằng ở đó.
Potatoswatter

@Dustin thực sự làm điều đó, từ các tài liệu: "Như một biện pháp phòng ngừa, @synchronizedkhối ngầm thêm một trình xử lý ngoại lệ vào mã được bảo vệ. Trình xử lý này tự động giải phóng mutex trong trường hợp ném ngoại lệ."
Pieter

objc_sync_enter có thể sẽ sử dụng mutex pthread, vì vậy biến đổi của Louis sâu hơn và chính xác.
jack

3

Việc triển khai @synyncized của Apple là nguồn mở và nó có thể được tìm thấy ở đây . Mike ash đã viết hai bài thực sự thú vị về chủ đề này:

Tóm lại, nó có một bảng ánh xạ các con trỏ đối tượng (sử dụng địa chỉ bộ nhớ của chúng làm khóa) để pthread_mutex_tkhóa, được khóa và mở khóa khi cần thiết.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.