多線程編程中庭砍,應該盡量避免資源在線程之間共享渐裂,以減少線程間的相互作用阳液。 但是總是有多個線程相互干擾的情況(如多個線程訪問一個資源)繁疤。在線程必須交互的情況下咖为,就需要一些同步工具,來確保當它們交互的時候是安全的稠腊。
鎖是線程編程同步工具的基礎躁染。iOS開發(fā)中常用的鎖有如下幾種:
- @synchronized
- NSLock 對象鎖
- NSRecursiveLock 遞歸鎖
- NSConditionLock 條件鎖
- pthread_mutex 互斥鎖(C語言)
- dispatch_semaphore 信號量實現(xiàn)加鎖(GCD)
- OSSpinLock (暫不建議使用,原因參見這里)
下圖是它們的性能對比:
- ** @synchronized 關(guān)鍵字加鎖 互斥鎖架忌,性能較差不推薦使用**
@synchronized(這里添加一個OC對象吞彤,一般使用self) {
這里寫要加鎖的代碼
}
注意點
1.加鎖的代碼盡量少
2.添加的OC對象必須在多個線程中都是同一對象
3.優(yōu)點是不需要顯式的創(chuàng)建鎖對象,便可以實現(xiàn)鎖的機制叹放。
4. @synchronized塊會隱式的添加一個異常處理例程來保護代碼饰恕,該處理例程會在異常拋出的時候自動的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來額外的開銷井仰,你可以考慮使用鎖對象埋嵌。
下面通過 賣票的例子 展示使用
//設置票的數(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方法,會造成死鎖**
在Cocoa程序中NSLock中實現(xiàn)了一個簡單的互斥鎖。
所有鎖(包括NSLock)的接口實際上都是通過NSLocking協(xié)議定義的俱恶,它定義了lock
和unlock
方法雹嗦。你使用這些方法來獲取和釋放該鎖。
NSLock類還增加了tryLock
和lockBeforeDate:
方法合是。
tryLock
試圖獲取一個鎖了罪,但是如果鎖不可用的時候,它不會阻塞線程端仰,相反捶惜,它只是返回NO。
lockBeforeDate:
方法試圖獲取一個鎖荔烧,但是如果鎖沒有在規(guī)定的時間內(nèi)被獲得吱七,它會讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)。
還是賣票的例子
//設置票的數(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 遞歸鎖**
使用鎖最容易犯的一個錯誤就是在遞歸或循環(huán)中造成死鎖
如下代碼中鹤竭,因為在線程1中的遞歸block中踊餐,鎖會被多次的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,而不會造成死鎖。
遞歸鎖會跟蹤它被多少次lock窜管。每次成功的lock都必須平衡調(diào)用unlock操作散劫。
只有所有的鎖住和解鎖操作都平衡的時候,鎖才真正被釋放給其他線程獲得幕帆。
//創(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:
在開鎖的同時設置了一個整型的條件 2 常熙。
線程2則需要一把被標識為2的鑰匙,所以當線程1循環(huán)到 i = 2 時碱茁,線程2的任務才執(zhí)行裸卫。
NSConditionLock也跟其它的鎖一樣,是需要lock與unlock對應的纽竣,只是lock,lockWhenCondition:與unlock墓贿,unlockWithCondition:是可以隨意組合的,當然這是與你的需求相關(guān)的退个。
- pthread_mutex 互斥鎖
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//線程1
dispatch_async(self.concurrentQueue), ^{
pthread_mutex_lock(&mutex);
NSLog(@"任務1");
sleep(2);
pthread_mutex_unlock(&mutex);
});
//線程2
dispatch_async(self.concurrentQueue), ^{
sleep(1);
pthread_mutex_lock(&mutex);
NSLog(@"任務2");
pthread_mutex_unlock(&mutex);
});
-
dispatch_semaphore 信號量實現(xiàn)加鎖
GCD中也已經(jīng)提供了一種信號機制募壕,使用它我們也可以來構(gòu)建一把”鎖”(從本質(zhì)意義上講,信號量與鎖是有區(qū)別语盈,請看互斥鎖與信號量的作用與區(qū)別):
// 創(chuàng)建信號量
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(@"任務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(@"任務2");
dispatch_semaphore_signal(semaphore);
});
- OSSpinLock
OSSpinLock 在圖1.1 中顯示的效率最高(暫不建議使用舱馅,原因參見這里)
//設置票的數(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);
}
}