iOS線程安全的鎖與性能對比
一、鎖的基本使用方法
1.1、@synchronized
這是我們最熟悉的枷鎖方式,用起來也比較簡單。使用時@synchronized后面需要緊跟一個OC對象,它實際上是把這個對象當做鎖來使用,作為該鎖的唯一標識,只有當標識相同時,才會滿足互斥。底層原理是當你調(diào)用sychronized的每個對象,runtime都會為其分配一個遞歸鎖并儲存在哈希表中。@synchronized原理
NSObject *obj = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (obj) {
NSLog(@"需要線程同步的操作1 開始");
sleep(3);
NSLog(@"需要線程同步的操作1 結(jié)束");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized (obj) {
NSLog(@"需要線程同步的操作 2 ");
}
});
執(zhí)行結(jié)果很顯然,等第一個線程結(jié)束后,才執(zhí)行到第二個線程中
1.2、dispatch_semaphore
dispatch_semaphore是GCD用來同步的一種方式,與他相關的共有三個函數(shù),分別是dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait。
這個函數(shù)會使傳入的信號量dsema的值減1;如果dsema信號量的值大于0,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語句,并且將信號量的值減1;如果desema的值為0,那么這個函數(shù)就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t,不能直接傳入整形或float型數(shù)),如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續(xù)向下執(zhí)行并將信號量減1。如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執(zhí)行其后語句。
dispatch_semaphore 是信號量,但當信號總量設為 1 時也可以當作鎖來。在沒有等待情況出現(xiàn)時,它的性能比 pthread_mutex 還要高,但一旦有等待情況出現(xiàn)時,性能就會下降許多。相對于 OSSpinLock 來說,它的優(yōu)勢在于等待時不會消耗 CPU 資源。
如上的代碼,如果超時時間overTime設置成>2,可完成同步操作。如果overTime<2的話,在線程1還沒有執(zhí)行完成的情況下,此時超時了,將自動執(zhí)行下面的代碼。
dispatch_semaphore_t signal = dispatch_semaphore_create(1); // 信號量為1
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime); // 信號量減1 ; overtime 超時時間 過了超時時間不會等待
NSLog(@"需要線程同步的操作1 開始");
sleep(2);
NSLog(@"需要線程同步的操作1 結(jié)束");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要線程同步的操作2 ");
dispatch_semaphore_signal(signal);
});
上面代碼的執(zhí)行結(jié)果:
2017-09-05 14:16:06.691 demo_lock[16669:271470] 需要線程同步的操作1 開始
2017-09-05 14:16:08.693 demo_lock[16669:271470] 需要線程同步的操作1 結(jié)束
2017-09-05 14:16:09.696 demo_lock[16669:271453] 需要線程同步的操作2
把overTime設置成<=2s 執(zhí)行結(jié)果為 :
2017-09-05 14:21:18.422 demo_lock[16746:280666] 需要線程同步的操作1 開始
2017-09-05 14:21:20.428 demo_lock[16746:280667] 需要線程同步的操作2
2017-09-05 14:21:20.428 demo_lock[16746:280666] 需要線程同步的操作1 結(jié)束
1.3、NSLock
NSLock 遵循 NSLocking 協(xié)議,lock 方法是加鎖,unlock 是解鎖,tryLock 是嘗試加鎖,如果失敗的話返回 NO,lockBeforeDate: 是在指定Date之前嘗試加鎖,如果在指定時間之前都不能加鎖,則返回NO。
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"線程1 開始");
sleep(2);
NSLog(@"線程1 結(jié)束");
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); // 保證線程二的代碼后執(zhí)行
[lock lock]; // lock的第一秒 一直輪詢請求枷鎖, 后面階段會進入waiting狀態(tài)
NSLog(@"線程2 開始");
[lock unlock];
});
控制臺輸出結(jié)果:
2017-09-05 15:08:33.782 demo_lock[17411:327912] 線程1 開始
2017-09-05 15:08:35.786 demo_lock[17411:327912] 線程1 結(jié)束
2017-09-05 15:08:35.786 demo_lock[17411:327911] 線程2 開始
線程 1 中的 lock 鎖上了,所以線程 2 中的 lock 加鎖失敗,阻塞線程 2,但 2 s 后線程 1 中的 lock 解鎖,線程 2 就立即加鎖成功,執(zhí)行線程 2 中的后續(xù)代碼。
查到的資料顯示互斥鎖會使得線程阻塞,阻塞的過程又分兩個階段,第一階段是會先空轉(zhuǎn),可以理解成跑一個 while 循環(huán),不斷地去申請加鎖,在空轉(zhuǎn)一定時間之后,線程會進入 waiting 狀態(tài),此時線程就不占用CPU資源了,等鎖可用的時候,這個線程會立即被喚醒。
所以如果將上面線程 1 中的 sleep(2); 改成 sleep(10); 輸出的結(jié)果會變成:
2017-09-05 15:09:30.271 demo_lock[17431:329738] 線程1 開始
2017-09-05 15:09:40.277 demo_lock[17431:329738] 線程1 結(jié)束
2017-09-05 15:09:40.277 demo_lock[17431:329740] 線程2 開始
NSLock只是在內(nèi)部封裝了一個pthread_mutex, 屬性為PTHREAD_MUTEX_ERRORCHECK
1.4、NSRecursiveLock遞歸鎖
遞歸鎖也是通過 pthread_mutex_lock 函數(shù)來實現(xiàn),在函數(shù)內(nèi)部會判斷鎖的類型。NSRecursiveLock 與 NSLock 的區(qū)別在于內(nèi)部封裝的 pthread_mutex_t 對象的類型不同,前者的類型為 PTHREAD_MUTEX_RECURSIVE。
// NSLock *lock = [[NSLock alloc] init];
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(1);
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
如果是NSLock這段代碼是一個典型的死鎖情況。在我們的線程中,RecursiveMethod是遞歸調(diào)用的。所以每次進入這個block時,都會去加一次鎖,而從第二次開始,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除,這樣就導致了死鎖,線程被阻塞住了。調(diào)試器中會輸出如下信息:
2017-09-05 16:01:29.158 demo_lock[18197:370719] value = 5
2017-09-05 16:01:30.161 demo_lock[18197:370719] *** -[NSLock lock]: deadlock (<NSLock: 0x6000000cd120> '(null)')
2017-09-05 16:01:30.161 demo_lock[18197:370719] *** Break on _NSLockError() to debug.
在這種情況下,我們就可以使用NSRecursiveLock。它可以允許同一線程多次加鎖,而不會造成死鎖。遞歸鎖會跟蹤它被lock的次數(shù)。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達到這種平衡,鎖最后才能被釋放,以供其它線程使用。
我們將NSLock替換為NSRecursiveLock,上面代碼則會正確執(zhí)行
2017-09-05 16:02:56.521 demo_lock[18223:372981] value = 5
2017-09-05 16:02:57.526 demo_lock[18223:372981] value = 4
2017-09-05 16:02:58.531 demo_lock[18223:372981] value = 3
2017-09-05 16:02:59.531 demo_lock[18223:372981] value = 2
2017-09-05 16:03:00.537 demo_lock[18223:372981] value = 1
1.5、NSConditionLock條件鎖
當我們在使用多線程的時候,有時一把只會lock和unlock的鎖未必就能完全滿足我們的使用。因為普通的鎖只能關心鎖與不鎖,而不在乎用什么鑰匙才能開鎖,而我們在處理資源共享的時候,多數(shù)情況是只有滿足一定條件的情況下才能打開這把鎖:
NSMutableArray *products = [NSMutableArray array];
NSConditionLock *lock = [[NSConditionLock alloc] init];
NSInteger HAS_DATA = 1;
NSInteger NO_DATA = 0;
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
while (1) {
[lock lockWhenCondition:NO_DATA];
[products addObject:[[NSObject alloc] init]];
NSLog(@"produce a product, 總量: %zi", products.count);
[lock unlockWithCondition:HAS_DATA];
sleep(1);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
NSLog(@"wait for product");
[lock lockWhenCondition:HAS_DATA];
[products removeObjectAtIndex:0];
NSLog(@"custome a product");
[lock unlockWithCondition:NO_DATA];
}
});
在線程1中的加鎖使用了lock,所以是不需要條件的,所以順利的就鎖住了,但在unlock的使用了一個整型的條件,它可以開啟其它線程中正在等待這把鑰匙的臨界地,而線程2則需要一把被標識為2的鑰匙,所以當線程1循環(huán)到最后一次的時候,才最終打開了線程2中的阻塞。但即便如此,NSConditionLock也跟其它的鎖一樣,是需要lock與unlock對應的,只是lock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,當然這是與你的需求相關的。
上面代碼執(zhí)行結(jié)果如下:
2017-09-05 16:55:21.740 demo_lock[19324:419225] wait for product
2017-09-05 16:55:21.740 demo_lock[19324:419224] produce a product, 總量: 1
2017-09-05 16:55:21.741 demo_lock[19324:419225] custome a product
2017-09-05 16:55:21.741 demo_lock[19324:419225] wait for product
2017-09-05 16:55:22.743 demo_lock[19324:419224] produce a product, 總量: 1
2017-09-05 16:55:22.743 demo_lock[19324:419225] custome a product
2017-09-05 16:55:22.744 demo_lock[19324:419225] wait for product
1.6、NSCondition條件鎖
NSCondition *condition = [[NSCondition alloc] init];
NSMutableArray *products = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
if ([products count] == 0) {
NSLog(@"wait for product");
[condition wait];
}
[products removeObjectAtIndex:0];
NSLog(@"custome a product");
[condition unlock];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
[products addObject:[[NSObject alloc] init]];
NSLog(@"produce a product,總量:%zi",products.count);
[condition signal];
[condition unlock];
sleep(1);
}
});
最基本的條件鎖。手動控制線程wait和signal
[condition lock];一般用于多線程同時訪問、修改同一個數(shù)據(jù)源,保證在同一時間內(nèi)數(shù)據(jù)源只被訪問、修改一次,其他線程的命令需要在lock 外等待,只到unlock ,才可訪問
[condition unlock];與lock 同時使用
[condition wait];讓當前線程處于等待狀態(tài)
[condition signal];CPU發(fā)信號告訴線程不用在等待,可以繼續(xù)執(zhí)行
上面代碼執(zhí)行結(jié)果如下:
2017-09-06 11:00:38.925 demo_lock[5145:119574] wait for product
2017-09-06 11:00:42.647 demo_lock[5145:119577] produce a product,總量:1
2017-09-06 11:00:55.847 demo_lock[5145:119574] custome a product
2017-09-06 11:01:05.752 demo_lock[5145:119574] wait for product
2017-09-06 11:01:08.262 demo_lock[5145:119577] produce a product,總量:1
2017-09-06 11:01:12.366 demo_lock[5145:119574] custome a product
2017-09-06 11:01:12.366 demo_lock[5145:119574] wait for product
1.7、pthread_mutex
c 語言定義下多線程枷鎖方式
1:pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t attr);
初始化鎖變量mutex。attr為鎖屬性,NULL值為默認屬性。
2:pthread_mutex_lock(pthread_mutex_t* mutex);加鎖3:pthread_mutex_tylock(*pthread_mutex_t** *mutex);加鎖,但是與2不一樣的是當鎖已經(jīng)在使用的時候,返回為EBUSY,而不是掛起等待。4:pthread_mutex_unlock(pthread_mutex_t *mutex);釋放鎖5:pthread_mutex_destroy(pthread_mutex_t *mutex);使用完后釋放
__block pthread_mutex_t theLock;
pthread_mutex_init(&theLock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&theLock);
NSLog(@"需要線程同步的操作1 開始");
sleep(4);
NSLog(@"需要線程同步的操作1 結(jié)束");
pthread_mutex_unlock(&theLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
pthread_mutex_lock(&theLock);
NSLog(@"需要線程同步的操作 2 ");
pthread_mutex_unlock(&theLock);
});
代碼執(zhí)行操作結(jié)果如下
2017-09-06 14:24:01.867 demo_lock[19902:324468] 需要線程同步的操作1 開始
2017-09-06 14:24:05.871 demo_lock[19902:324468] 需要線程同步的操作1 結(jié)束
2017-09-06 14:24:05.872 demo_lock[19902:324469] 需要線程同步的操作 2
1.8、pthread_mutex(recursive)遞歸鎖
__block pthread_mutex_t theLock;
// pthread_mutex_init(&theLock, NULL);
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&theLock, &attr);
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^ RecursiveMethod)(int);
RecursiveMethod = ^ (int value) {
pthread_mutex_lock(&theLock);
if (value > 0) {
NSLog(@"value = %d", value);
sleep(1);
RecursiveMethod(value - 1);
}
pthread_mutex_unlock(&theLock);
};
RecursiveMethod(5);
});
這是pthread_mutex為了防止在遞歸的情況下出現(xiàn)死鎖而出現(xiàn)的遞歸鎖。作用和NSRecursiveLock遞歸鎖類似。
如果使用pthread_mutex_init(&theLock, NULL);初始化鎖的話,上面的代碼會出現(xiàn)死鎖現(xiàn)象。如果使用遞歸鎖的形式,則沒有問題。
打印臺輸出結(jié)果如下
2017-09-06 14:26:54.450 demo_lock[19975:329591] value = 5
2017-09-06 14:26:55.455 demo_lock[19975:329591] value = 4
2017-09-06 14:26:56.456 demo_lock[19975:329591] value = 3
2017-09-06 14:26:57.457 demo_lock[19975:329591] value = 2
2017-09-06 14:26:58.458 demo_lock[19975:329591] value = 1
二、鎖的性能對比
在 ibireme 的 不再安全的 OSSpinLock 一文中,有一張圖片簡單的比較了各種鎖的加解鎖性能:

三、實現(xiàn)原理
自旋鎖的實現(xiàn)原理
自旋鎖的目的是為了確保臨界區(qū)只有一個線程可以訪問,偽代碼
do {
Acquire Lock
Critical section // 臨界區(qū)
Release Lock
Reminder section // 不需要鎖保護的代碼
}
自旋鎖的實現(xiàn)思路很簡單,理論上來說只要定義一個全局變量,用來表示鎖的可用情況即可,偽代碼如下
bool lock = false; // 一開始沒有鎖上,任何線程都可以申請鎖
do {
while(lock); // 如果 lock 為 true 就一直死循環(huán),相當于申請鎖
lock = true; // 掛上鎖,這樣別的線程就無法獲得鎖
Critical section // 臨界區(qū)
lock = false; // 相當于釋放鎖,這樣別的線程可以進入臨界區(qū)
Reminder section // 不需要鎖保護的代碼
}
這段代碼存在一個問題: 如果一開始有多個線程同時執(zhí)行 while 循環(huán),他們都不會在這里卡住,而是繼續(xù)執(zhí)行,這樣就無法保證鎖的可靠性了。解決思路也很簡單,只要確保申請鎖的過程是原子操作即可。
原子操作
狹義上:表示一條不可打斷的操作,在線程執(zhí)行操作過程中,不會被操作系統(tǒng)掛起,一定要執(zhí)行完畢
在多處理器的情況下,能夠被多個處理器同時執(zhí)行的操作不能算原子操作。因此,真正的原子操作必須由硬件提供支持。
我們只需要知道申請鎖的過程,可以用一個原子性操作test_and_set來完成 用偽代碼這樣表示
bool test_and_set (bool *target) {
bool rv = *target;
*target = TRUE;
return rv;
}
自旋鎖的總結(jié)
原理
bool lock = false; // 一開始沒有鎖上,任何線程都可以申請鎖
do {
while(test_and_set(&lock); // test_and_set 是一個原子操作
Critical section // 臨界區(qū)
lock = false; // 相當于釋放鎖,這樣別的線程可以進入臨界區(qū)
Reminder section // 不需要鎖保護的代碼
}
如果臨界區(qū)的執(zhí)行時間過長,使用自旋鎖就不是個好主意 ;臨界區(qū)執(zhí)行時間較長,比如是文件讀寫,這種忙等是毫無必要的。
信號量
int sem_wait (sem_t *sem) {
int *futex = (int *) sem;
if (atomic_decrement_if_positive (futex) > 0)
return 0;
int err = lll_futex_wait (futex, 0);
return -1;
)
首先會把信號量的值減一,并判斷是否大于0,如果大于0,說明不用等待,立刻返回。等待操作在lll_futex_wait函數(shù)中實現(xiàn)。使線程進入睡眠狀態(tài),主動讓出時間片,這個函數(shù)在互斥鎖的實現(xiàn)中,也有可能被用到。
主動讓出時間片并不代表效率高,會導致操作系統(tǒng)切換到另一個線程。上下文切換通常需要10ms,而且需要切換兩次。如果等待時間很短,比如只有幾微秒,忙等就比線程休眠更高效。
可以看出自旋鎖和信號量的實現(xiàn)都非常簡單,這也是兩者的加解鎖時間分別排在第一和第二的原因。
加解鎖耗時不能準確反應出鎖的效率,只能從一定程度上衡量鎖的實現(xiàn)負責程度。
pthread_mutex
phread表示POSIX thread,定義了一組跨平臺的線程相關的API,pthread_mutex表示互斥鎖?;コ怄i的實現(xiàn)原理與信號量非常相似,不是使用忙等,而是阻塞線程并睡眠,需要進行上下文切換。
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 定義鎖的屬性
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr) // 創(chuàng)建鎖
pthread_mutex_lock(&mutex); // 申請鎖
// 臨界區(qū)
pthread_mutex_unlock(&mutex); // 釋放鎖
對于 pthread_mutex 來說,它的用法和之前沒有太大的改變,比較重要的是鎖的類型,可以有 PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE 等等,具體的特性就不做解釋了,網(wǎng)上有很多相關資料。
一般情況下,一個線程只能申請一次鎖,也只能在獲得鎖的情況下才能釋放鎖,多次申請鎖或釋放未獲得的鎖都會導致崩潰。假設在已經(jīng)獲得鎖的情況下再次申請鎖,線程會因為等待鎖的釋放而進入睡眠狀態(tài),因此就不可能再釋放鎖,從而導致死鎖。
然而這種情況經(jīng)常會發(fā)生,比如某個函數(shù)申請了鎖,在臨界區(qū)內(nèi)又遞歸調(diào)用了自己。辛運的是 pthread_mutex 支持遞歸鎖,也就是允許一個線程遞歸的申請鎖,只要把 attr 的類型改成 PTHREAD_MUTEX_RECURSIVE 即可。
互斥鎖的實現(xiàn)
互斥鎖在申請鎖時,調(diào)用了 pthread_mutex_lock 方法,它在不同的系統(tǒng)上實現(xiàn)各有不同,有時候它的內(nèi)部是使用信號量來實現(xiàn),即使不用信號量,也會調(diào)用到 lll_futex_wait 函數(shù),從而導致線程休眠。
上文說到如果臨界區(qū)很短,忙等的效率也許更高,所以在有些版本的實現(xiàn)中,會首先嘗試一定次數(shù)(比如 1000 次)的 test_and_test,這樣可以在錯誤使用互斥鎖時提高性能。
另外,由于 pthread_mutex 有多種類型,可以支持遞歸鎖等,因此在申請加鎖時,需要對鎖的類型加以判斷,這也就是為什么它和信號量的實現(xiàn)類似,但效率略低的原因。
NSLock
NSLock是Objective-C 已對象的形式暴露給開發(fā)者的一種鎖,實現(xiàn)簡單,通過宏,定義了lock方法:
#define MLOCK \
- (void) lock\
{\
int err = pthread_mutex_lock(&_mutex);\
// 錯誤處理 ……
}
NSLock在內(nèi)部封裝了phread_mutex,屬性類型為PTHREAD_MUTEX_ERRORCHECK,它會損失一定性能換來錯誤提示。
NSLock比pthread_mutex略慢的原因是在于它需要經(jīng)過方法調(diào)用,但是由于緩存的存在,多次方法調(diào)用不會對性能產(chǎn)生太大影響。
NSCondition
NSCondititon的底層是通過條件變量(condition variable)pthread_cond_t來實現(xiàn)的。條件變量有些像信號量,提供了線程阻塞與信號機制,因此可以用來阻塞某個線程,并等待某個數(shù)據(jù)就緒,隨后喚醒線程,比如常見的生產(chǎn)者-消費模式
很多介紹pthread_cond_t的文章都會提到,它需要與互斥鎖配合使用
void consumer () { // 消費者
pthread_mutex_lock(&mutex);
while (data == NULL) {
pthread_cond_wait(&condition_variable_signal, &mutex); // 等待數(shù)據(jù)
}
// --- 有新的數(shù)據(jù),以下代碼負責處理 ↓↓↓↓↓↓
// temp = data;
// --- 有新的數(shù)據(jù),以上代碼負責處理 ↑↑↑↑↑↑
pthread_mutex_unlock(&mutex);
}
void producer () {
pthread_mutex_lock(&mutex);
// 生產(chǎn)數(shù)據(jù)
pthread_cond_signal(&condition_variable_signal); // 發(fā)出信號給消費者,告訴他們有了新的數(shù)據(jù)
pthread_mutex_unlock(&mutex);
}
NSCondition的做法
NSCondition其實是封裝了一個互斥鎖和條件變量,把前者的lock方法和后者的wait/signal統(tǒng)一在NSCondition對象中,暴露給使用者:
- (void) signal {
pthread_cond_signal(&_condition);
}
// 其實這個函數(shù)是通過宏來定義的,展開后就是這樣
- (void) lock {
int err = pthread_mutex_lock(&_mutex);
}
NSRecursiveLock
上文已經(jīng)說過,遞歸鎖也是通過 pthread_mutex_lock 函數(shù)來實現(xiàn),在函數(shù)內(nèi)部會判斷鎖的類型,如果顯示是遞歸鎖,就允許遞歸調(diào)用,僅僅將一個計數(shù)器加一,鎖的釋放過程也是同理。
NSRecursiveLock 與 NSLock 的區(qū)別在于內(nèi)部封裝的 pthread_mutex_t 對象的類型不同,前者的類型為 PTHREAD_MUTEX_RECURSIVE。
NSConditionLock
NSConditionLock借助NSCondition來實現(xiàn),它的本質(zhì)就是一個生產(chǎn)者-消費者模型?!皸l件被滿足”可以理解為生產(chǎn)者提供了新的內(nèi)容。NSConditionLock 的內(nèi)部持有一個 NSCondition 對象,以及 _condition_value 屬性,在初始化時就會對這個屬性進行賦值:
- (id) initWithCondition: (NSInteger)value {
if (nil != (self = [super init])) {
_condition = [NSCondition new]
_condition_value = value;
}
return self;
}
它的lockWhenCondition方法其實就是消費者方法
- (void) lockWhenCondition: (NSInteger)value {
[_condition lock];
while (value != _condition_value) {
[_condition wait];
}
}
對應的unlockWhenCondition方法則是生產(chǎn)者,使用了broadcast方法通知了所有的消費者
- (void) unlockWithCondition: (NSInteger)value {
_condition_value = value;
[_condition broadcast];
[_condition unlock];
}
@synchronized
這其實是一個 OC 層面的鎖, 主要是通過犧牲性能換來語法上的簡潔與可讀。
我們知道 @synchronized 后面需要緊跟一個 OC 對象,它實際上是把這個對象當做鎖來使用。這是通過一個哈希表來實現(xiàn)的,OC 在底層使用了一個互斥鎖的數(shù)組(你可以理解為鎖池),通過對對象去哈希值來得到對應的互斥鎖。