6

Tôi có một lớp khách quan-c với một số phương thức, sử dụng hàng đợi GCD để đảm bảo rằng việc truy cập đồng thời vào một tài nguyên diễn ra theo cách serially (cách tiêu chuẩn để thực hiện điều này).Làm thế nào để thực hiện một cơ chế khóa reentrant trong mục tiêu-c thông qua GCD?

Một số phương pháp này cần gọi các phương thức khác của cùng một lớp. Vì vậy, cơ chế khóa cần phải được tái nhập cảnh. Có cách nào tiêu chuẩn để làm điều này không?

Lúc đầu, tôi đã từng trong những phương pháp sử dụng

dispatch_sync(my_queue, ^{ 

    // Critical section 

}); 

để đồng bộ hóa các truy cập. Như bạn đã biết, khi một trong các phương thức này gọi một phương thức như vậy, một bế tắc xảy ra bởi vì lệnh dispatch_sync dừng thực hiện hiện tại cho đến khi khối khác được thực thi, cũng không thể thực thi được, vì việc thực hiện trên hàng đợi bị dừng. Để giải quyết điều này, tôi đã sử dụng ví dụ: phương pháp này:

- (void) executeOnQueueSync:(dispatch_queue_t)queue : (void (^)(void))theBlock { 
    if (dispatch_get_current_queue() == queue) { 
     theBlock(); 
    } else { 
     dispatch_sync(queue, theBlock); 
    } 
} 

Và trong mỗi phương pháp của tôi, tôi sử dụng

[self executeOnQueueSync:my_queue : ^{ 

    // Critical section 

}]; 

Tôi không thích giải pháp này, bởi vì đối với mỗi khối với một kiểu trả về khác nhau, tôi cần phải viết phương pháp khác. Hơn nữa, vấn đề này có vẻ rất phổ biến với tôi và tôi nghĩ rằng nên tồn tại một giải pháp tốt hơn, tiêu chuẩn cho việc này.

+0

Bạn có cân nhắc sử dụng '@ synchronized' để thay thế không? –

+1

@MartinR, vâng, nhưng '@ synchronized' là một khóa cổ điển, không dựa trên GCD/hàng đợi, và do đó, như tôi đã hiểu, nó không được khuyến khích sử dụng vì lý do đơn giản và hiệu suất mã. Tiêu đề của câu hỏi này là do đó gây hiểu lầm, bởi vì nó chứa "khóa". Những gì tôi có nghĩa là đồng bộ hóa truy cập một cách reentrant với GDC/hàng đợi. Tôi đơn giản không có lời nào tốt hơn là nói "khóa reentrant", bởi vì đó là tên của giải pháp cho vấn đề mà hầu hết mọi người đều biết. –

+1

@MartinR, hơn nữa mặc dù '@ synchronized' có lợi thế là dẫn đến mã đơn giản hơn cho reentrant -whatever- (cách gọi này là tốt nhất?). Tuy nhiên, cơ chế khóa đằng sau nó có hiệu suất kém hơn so với cơ chế đằng sau hàng đợi GCD. –

Trả lời

10

Điều đầu tiên trước tiên: dispatch_get_current_queue() không còn được dùng nữa. Cách tiếp cận kinh điển bây giờ sẽ là sử dụng dispatch_queue_set_specific. Một ví dụ có thể trông giống như:

typedef dispatch_queue_t dispatch_recursive_queue_t; 
static const void * const RecursiveKey = (const void*)&RecursiveKey; 

dispatch_recursive_queue_t dispatch_queue_create_recursive_serial(const char * name) 
{ 
    dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL); 
    dispatch_queue_set_specific(queue, RecursiveKey, (__bridge void *)(queue), NULL); 
    return queue; 
} 

void dispatch_sync_recursive(dispatch_recursive_queue_t queue, dispatch_block_t block) 
{ 
    if (dispatch_get_specific(RecursiveKey) == (__bridge void *)(queue)) 
     block(); 
    else 
     dispatch_sync(queue, block); 
} 

mô hình này là khá hữu dụng, nhưng nó cho là không chống đạn, bởi vì bạn có thể tạo hàng đợi đệ quy lồng nhau với dispatch_set_target_queue, và cố gắng để enqueue làm việc trên hàng đợi bên ngoài từ bên trong một nội sẽ bế tắc, mặc dù bạn đã "bên trong khóa" (trong dấu ngoặc kép vì nó chỉ trông giống như một khóa, nó thực sự là một cái gì đó khác nhau: một hàng đợi - do đó câu hỏi, phải không?) cho bên ngoài. (Bạn có thể nhận được xung quanh đó bằng cách gói các cuộc gọi đến dispatch_set_target_queue và duy trì của riêng bạn out-of-band nhắm đồ thị, vv, nhưng những gì còn lại như một bài tập cho người đọc.)

Bạn tiếp tục nói:

Tôi không thích giải pháp này, bởi vì đối với mỗi khối với một loại trả về khác nhau , tôi cần viết một phương thức khác.

Ý tưởng chung của mẫu "hàng đợi bảo vệ nối tiếp" này là bạn đang bảo vệ trạng thái riêng tư; tại sao bạn lại "mang hàng đợi của riêng mình" vào điều này? Nếu đó là về nhiều đối tượng chia sẻ bảo vệ nhà nước, sau đó cung cấp cho họ một cách cố hữu để tìm hàng đợi (tức là, hoặc đẩy nó vào lúc init, hoặc đặt nó ở đâu đó có thể truy cập lẫn nhau cho tất cả các bên quan tâm). Nó không rõ ràng làm thế nào "mang hàng đợi của riêng bạn" sẽ hữu ích ở đây.

+2

Cảm ơn bạn đã giải thích về chức năng không dùng nữa và cách sử dụng và cách sử dụng. Điều đó một mình là giá trị rất nhiều. –

+0

@ipmcc, tôi thực sự ấn tượng bởi câu trả lời và nhận xét của bạn cho cả chủ đề này và chủ đề được liên kết. Bạn có thể vui lòng xem [câu hỏi này] (http://stackoverflow.com/questions/20201078/how-to-implement-a-reentrant-locking-mechanism-through-dispatch-concurrent-queue) Tôi vừa mới đăng ? –

+0

@ipmcc Có sự khác biệt về hiệu quả hoặc hiệu quả với phương pháp này trái ngược với việc sử dụng hàng đợi có nhãn & 'dispatch_queue_get_label (DISPATCH_CURRENT_QUEUE_LABEL)' không? – Orangenhain

Các vấn đề liên quan