NSRecursiveLock實際上定義的是一個遞歸鎖,這個鎖可以被同一線程多次請求,而不會引起死鎖。這主要是用在循環或遞歸操作中。我們先來看一個示例:
NSLock *lock = [[NSLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveMethod)(int); RecursiveMethod = ^(int value) { [lock lock]; if (value > 0) { NSLog(@"value = %d", value); sleep(2); RecursiveMethod(value - 1); } [lock unlock]; }; RecursiveMethod(5); });
這段代碼是一個典型的死鎖情況。在我們的線程中,RecursiveMethod是遞歸調用的。所以每次進入這個block時,都會去加一次鎖,而從第二次開始,由於鎖已經被使用了且沒有解鎖,所以它需要等待鎖被解除,這樣就導致了死鎖,線程被阻塞住了。調試器中會輸出如下信息:
value = 5 *** -[NSLock lock]: deadlock ( '(null)') *** Break on _NSLockError() to debug.
在這種情況下,我們就可以使用NSRecursiveLock。它可以允許同一線程多次加鎖,而不會造成死鎖。遞歸鎖會跟蹤它被lock的次數。每次成功的lock都必須平衡調用unlock操作。只有所有達到這種平衡,鎖最後才能被釋放,以供其它線程使用。
所以,對上面的代碼進行一下改造,
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
這樣,程序就能正常運行了,其輸出如下所示:
value = 5 value = 4 value = 3 value = 2 value = 1
NSRecursiveLock除了實現NSLocking協議的方法外,還提供了兩個方法,分別如下:
// 在給定的時間之前去嘗試請求一個鎖 - (BOOL)lockBeforeDate:(NSDate *)limit // 嘗試去請求一個鎖,並會立即返回一個布爾值,表示嘗試是否成功 - (BOOL)tryLock
這兩個方法都可以用於在多線程的情況下,去嘗試請求一個遞歸鎖,然後根據返回的布爾值,來做相應的處理。如下代碼所示:
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveMethod)(int); RecursiveMethod = ^(int value) { [lock lock]; if (value > 0) { NSLog(@"value = %d", value); sleep(2); RecursiveMethod(value - 1); } [lock unlock]; }; RecursiveMethod(5); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(2); BOOL flag = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; if (flag) { NSLog(@"lock before date"); [lock unlock]; } else { NSLog(@"fail to lock before date"); } });
在前面的代碼中,我們又添加了一段代碼,增加一個線程來獲取遞歸鎖。我們在第二個線程中嘗試去獲取遞歸鎖,當然這種情況下是會失敗的,輸出結果如下:
value = 5 value = 4 fail to lock before date value = 3 value = 2 value = 1
另外,NSRecursiveLock還聲明了一個name屬性,如下:
@property(copy) NSString *name
我們可以使用這個字符串來標識一個鎖。Cocoa也會使用這個name作為錯誤描述信息的一部分。
參考
NSRecursiveLock Class Reference
Objective-C中不同方式實現鎖(二)