多線程編程中,應(yīng)該盡量避免資源在線程之間共享,以減少線程間的相互作用。 但是總是有多個(gè)線程相互干擾的情況(如多個(gè)線程訪問一個(gè)資源)。在線程必須交互的情況下,就需要一些同步工具,來確保當(dāng)它們交互的時(shí)候是安全的。
鎖是線程編程同步工具的基礎(chǔ)。iOS開發(fā)中常用的鎖有如下幾種:
- @synchronized
- NSLock 對(duì)象鎖
- NSRecursiveLock 遞歸鎖
- NSConditionLock 條件鎖
- pthread_mutex 互斥鎖(C語言)
- dispatch_semaphore 信號(hào)量實(shí)現(xiàn)加鎖(GCD)
- OSSpinLock (暫不建議使用,原因參見這里)
下圖是它們的性能對(duì)比:

- ** @synchronized 關(guān)鍵字加鎖 互斥鎖,性能較差不推薦使用**
@synchronized(這里添加一個(gè)OC對(duì)象,一般使用self) {
這里寫要加鎖的代碼
}
注意點(diǎn)
1.加鎖的代碼盡量少
2.添加的OC對(duì)象必須在多個(gè)線程中都是同一對(duì)象
3.優(yōu)點(diǎn)是不需要顯式的創(chuàng)建鎖對(duì)象,便可以實(shí)現(xiàn)鎖的機(jī)制。
4. @synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對(duì)象。
下面通過 賣票的例子 展示使用
//設(shè)置票的數(shù)量為5
_tickets = 5;
//線程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
//線程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
- (void)saleTickets
{
while (1) {
@synchronized(self) {
[NSThread sleepForTimeInterval:1];
if (_tickets > 0) {
_tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
break;
}
}
}
}

- ** NSLock 互斥鎖 不能多次調(diào)用 lock方法,會(huì)造成死鎖**
在Cocoa程序中NSLock中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的互斥鎖。
所有鎖(包括NSLock)的接口實(shí)際上都是通過NSLocking協(xié)議定義的,它定義了lock和unlock方法。你使用這些方法來獲取和釋放該鎖。
NSLock類還增加了tryLock和lockBeforeDate:方法。
tryLock試圖獲取一個(gè)鎖,但是如果鎖不可用的時(shí)候,它不會(huì)阻塞線程,相反,它只是返回NO。
lockBeforeDate:方法試圖獲取一個(gè)鎖,但是如果鎖沒有在規(guī)定的時(shí)間內(nèi)被獲得,它會(huì)讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)。
還是賣票的例子
//設(shè)置票的數(shù)量為5
_tickets = 5;
//創(chuàng)建鎖
_mutexLock = [[NSLock alloc] init];
//線程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
//線程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
- (void)saleTickets
{
while (1) {
[NSThread sleepForTimeInterval:1];
//加鎖
[_mutexLock lock];
if (_tickets > 0) {
_tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
break;
}
//解鎖
[_mutexLock unlock];
}
}

- ** NSRecursiveLock 遞歸鎖**
使用鎖最容易犯的一個(gè)錯(cuò)誤就是在遞歸或循環(huán)中造成死鎖
如下代碼中,因?yàn)樵诰€程1中的遞歸block中,鎖會(huì)被多次的lock,所以自己也被阻塞了
//創(chuàng)建鎖
_mutexLock = [[NSLock alloc]init];
//線程1
dispatch_async(self.concurrentQueue, ^{
static void(^TestMethod)(int);
TestMethod = ^(int value)
{
[_mutexLock lock];
if (value > 0)
{
[NSThread sleepForTimeInterval:1];
TestMethod(value--);
}
[_mutexLock unlock];
};
TestMethod(5);
});
此處將NSLock換成NSRecursiveLock,便可解決問題。
NSRecursiveLock類定義的鎖可以在同一線程多次lock,而不會(huì)造成死鎖。
遞歸鎖會(huì)跟蹤它被多少次lock。每次成功的lock都必須平衡調(diào)用unlock操作。
只有所有的鎖住和解鎖操作都平衡的時(shí)候,鎖才真正被釋放給其他線程獲得。
//創(chuàng)建鎖
_rsLock = [[NSRecursiveLock alloc] init];
//線程1
dispatch_async(self.concurrentQueue, ^{
static void(^TestMethod)(int);
TestMethod = ^(int value)
{
[_rsLock lock];
if (value > 0)
{
[NSThread sleepForTimeInterval:1];
TestMethod(value--);
}
[_rsLock unlock];
};
TestMethod(5);
});
- ** NSConditionLock 條件鎖 **
直接看代碼和介紹
//主線程中
NSConditionLock *theLock = [[NSConditionLock alloc] init];
//線程1
dispatch_async(self.concurrentQueue, ^{
for (int i=0;i<=3;i++)
{
[theLock lock];
NSLog(@"thread1:%d",i);
sleep(1);
[theLock unlockWithCondition:i];
}
});
//線程2
dispatch_async(self.concurrentQueue, ^{
[theLock lockWhenCondition:2];
NSLog(@"thread2");
[theLock unlock];
});

在線程1中的加鎖使用了lock,是不需要條件的,所以順利的就鎖住了。
unlockWithCondition:在開鎖的同時(shí)設(shè)置了一個(gè)整型的條件 2 。
線程2則需要一把被標(biāo)識(shí)為2的鑰匙,所以當(dāng)線程1循環(huán)到 i = 2 時(shí),線程2的任務(wù)才執(zhí)行。
NSConditionLock也跟其它的鎖一樣,是需要lock與unlock對(duì)應(yīng)的,只是lock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,當(dāng)然這是與你的需求相關(guān)的。
- pthread_mutex 互斥鎖
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//線程1
dispatch_async(self.concurrentQueue), ^{
pthread_mutex_lock(&mutex);
NSLog(@"任務(wù)1");
sleep(2);
pthread_mutex_unlock(&mutex);
});
//線程2
dispatch_async(self.concurrentQueue), ^{
sleep(1);
pthread_mutex_lock(&mutex);
NSLog(@"任務(wù)2");
pthread_mutex_unlock(&mutex);
});
-
dispatch_semaphore 信號(hào)量實(shí)現(xiàn)加鎖
GCD中也已經(jīng)提供了一種信號(hào)機(jī)制,使用它我們也可以來構(gòu)建一把”鎖”(從本質(zhì)意義上講,信號(hào)量與鎖是有區(qū)別,請(qǐng)看互斥鎖與信號(hào)量的作用與區(qū)別):
// 創(chuàng)建信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務(wù)1");
sleep(10);
dispatch_semaphore_signal(semaphore);
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務(wù)2");
dispatch_semaphore_signal(semaphore);
});
- OSSpinLock
OSSpinLock 在圖1.1 中顯示的效率最高(暫不建議使用,原因參見這里)
//設(shè)置票的數(shù)量為5
_tickets = 5;
//創(chuàng)建鎖
_pinLock = OS_SPINLOCK_INIT;
//線程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
//線程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
- (void)saleTickets {
while (1) {
[NSThread sleepForTimeInterval:1];
//加鎖
OSSpinLockLock(&_pinLock);
if (_tickets > 0) {
_tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
break;
}
//解鎖
OSSpinLockUnlock(&_pinLock);
}
}
