前言
生活中的鎖隨處可見,鎖的作用也不言而喻,本文小結(jié)一下iOS的鎖。
技能表
- atomic (醬油君)
- @synchronized
- NSLock
- NSConditionLock
- NSRecursiveLock
- NSCondition
- dispatch_semaphore
- OSSpinLock
- os_unfair_lock
- POSIX LOCK
- NSDistributedLock (醬油君)
atomic
說到鎖不得不提線程安全,說到線程安全,不得不提nonatomic與atomic的愛恨情仇。
我們經(jīng)??吹竭@樣的描述:“nonatomic為非原子性非線程安全,atomic為原子性線程安全,但是atomic真的線程安全嗎?”
然后就沒有然后了。。
先來扒一下nonatomic和atomic會干什么
nonatomic/atomic = getter + setter + ivar
nonatomic生成的getter、setter沒加鎖,atomic生成的getter、setter有鎖。所以當(dāng)通過setter/getter而非ivar賦值/取值被atomic修飾的屬性時,該屬性是讀寫安全的。
然而讀寫安全并不代表線程安全,那么什么是線程安全?
線程安全就是多線程訪問時,采用了加鎖機制,當(dāng)一個線程訪問該類的某個數(shù)據(jù)時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。不會出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。 線程不安全就是不提供數(shù)據(jù)訪問保護,有可能出現(xiàn)多個線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù) 【引自百科】
- atomic非線程安全驗證
@interface ViewController ()
@property (strong) NSString *info;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
self.info = @"a";
NSLog(@"A--info:%@", self.info);
}
});
//B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
self.info = @"b";
NSLog(@"B--info:%@", self.info);
}
});
}
@end
根據(jù)線程安全定義,如果atomic為線程安全A輸出應(yīng)該永遠(yuǎn)為A--info:a,B輸出應(yīng)該永遠(yuǎn)為B--info:b
來看控制臺輸出

OK,atomic非線程安全驗證完畢,下面來說鎖。
@synchronized
@synchronized是iOS中最常見的鎖,用法很簡單
//A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
@synchronized (self) {
_info = @"a";
NSLog(@"A--info:%@", _info);
}
}
});
//B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
@synchronized (self) {
_info = @"b";
NSLog(@"B--info:%@", _info);
}
}
});
這樣就可以確保A中輸出均為A--info:a,B中輸出均為B--info:b
但是@synchronized()括號中只要寫相同數(shù)據(jù)就可以嗎?如果這個數(shù)據(jù)的地址在不斷變化呢?比如這樣:
//A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
@synchronized (_info) {
_info = @"a";
NSLog(@"A--info:%@", _info);
}
}
});
//B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
@synchronized (_info) {
_info = @"b";
NSLog(@"B--info:%@", _info);
}
}
});
再來看控制臺輸出:

可見@synchronized()括號中只能寫地址不變的數(shù)據(jù)。
@synchronized會隱式添加異常處理,當(dāng)發(fā)生異常時自動釋放互斥鎖,性能相對較低。
NSLock
NSLock是iOS中另一種較為常見的鎖,進入NSLock.h中可以發(fā)現(xiàn)NSLock繼承自NSObject并且遵守NSLocking協(xié)議。除此之外,在NSLock.h中還能看到NSConditionLock、NSRecursiveLock和NSCondition這3個類,他們也都是繼承自NSObject并且遵守NSLocking協(xié)議。
NSLocking協(xié)議定義了兩個實例方法,lock和unlock對應(yīng)著加鎖與解鎖
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
NSLock、NSConditionLock、NSRecursiveLock、NSCondition對應(yīng)的實例都可以通過lock/unlock來進行加鎖/解鎖。
代碼這樣寫就可以確保線程安全
//A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[_lock lock];
_info = @"a";
NSLog(@"A--info:%@", _info);
[_lock unlock];
}
});
//B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[_lock lock];
_info = @"b";
NSLog(@"B--info:%@", _info);
[_lock unlock];
}
});
注意:lock與unlock操作必須在同一線程,否則結(jié)果不確定甚至?xí)鹚梨i
除此之外,NSLock還提供另外兩個方法,見名知意,不做過多解釋。
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
NSConditionLock
NSConditionLock中有這么幾個方法
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
在初始化lock時給個condition,屬性condition為readonly,此時也是在給這個屬性賦值
偽代碼
- (instancetype)initWithCondition:(NSInteger)condition {
if (self =[ [NSConditionLock alloc] init]) {
[self setValue:@condition forKey:@"condition"];
}
return self;
}
利用condition加鎖、解鎖時偽代碼是這樣的
- (void)lockWhenCondition:(NSInteger)condition {
if (_condition == condition) [self lock];
}
- (void)unlockWithCondition:(NSInteger)condition {
[self setValue:@condition forKey:@"condition"];
[self unlock];
}
condition實現(xiàn)條件鎖時(也可以不實現(xiàn),直接調(diào)用協(xié)議方法lock),只有符合條件才能上鎖,但是解鎖為非條件,任意condition都可以解鎖,此時設(shè)置的condition為下一次條件鎖的condition。
線程安全示例代碼
//A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[_lock lockWhenCondition:0];
_info = @"a";
NSLog(@"A--info:%@--condition:%zd", _info, _lock.condition);
[_lock unlockWithCondition:1];
}
});
//B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[_lock lockWhenCondition:1];
_info = @"b";
NSLog(@"B--info:%@--condition:%zd", _info, _lock.condition);
[_lock unlockWithCondition:0];
}
});
利用這個特性,我們可以設(shè)置依賴關(guān)系。通常- (void)lock 與 - (void)unlockWithCondition:(NSInteger)condition 配合使用 ,- (void)lockWhenCondition:(NSInteger)condition 與- (void) unlock 配合使用,當(dāng)然也可以混用。
NSRecursiveLock
NSRecursiveLock翻譯成中文叫遞歸鎖,顧名思義可處理同一方法內(nèi)部多次上鎖的場景
static int i = 10;
- (void)recursiveLock {
[_lock lock];
NSLog(@"NSRecursiveLock--%zd", i--);
if (i >= 0) {
[self recursiveLock];
}
[_lock unlock];
}
如果把這里的lock換成NSLock顯然必死無疑(死鎖)。不同于其他lock,雖然NSRecursiveLock可以多次上鎖,但是只有當(dāng)上的所有鎖全被解鎖后,其他線程才能再次獲取到NSRecursiveLock
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self recursiveLock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[_lock lock];
NSLog(@"lock");
[_lock unlock];
NSLog(@"unlock");
}
});

NSCondition
NSCondition中有這些方法
- (void)wait; //掛起線程
- (BOOL)waitUntilDate:(NSDate *)limit; //什么時候掛起線程
- (void)signal; // 喚醒一條掛起線程
- (void)broadcast; //喚醒所有掛起線程
NSCondition可以手動控制線程的掛起與喚醒,很明顯可以利用這個特性設(shè)置依賴
基本用法:
//A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[_lock lock];
NSLog(@"A線程加鎖");
[_lock wait];
NSLog(@"A線程喚醒");
[_lock unlock];
NSLog(@"A線程解鎖");
});
//B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[_lock lock];
NSLog(@"B線程加鎖");
[_lock wait];
NSLog(@"B線程喚醒");
[_lock unlock];
NSLog(@"B線程解鎖");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
[_lock signal];
});

如果把[_lock signal]換成[_lock broadcast]

dispatch_semaphore
dispatch_semaphore利用信號量進行鎖定
線程安全示例代碼:
- (void)semaphore {
dispatch_semaphore_t dsema = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
_info = @"a";
NSLog(@"A--info:%@", _info);
dispatch_semaphore_signal(dsema);
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
_info = @"b";
NSLog(@"B--info:%@", _info);
dispatch_semaphore_signal(dsema);
}
});
}
/*!
* @param value
*信號量的起始值,當(dāng)傳入的值小于零時返回NULL
* @result
* 成功返回一個新的信號量,失敗返回NULL
*/
dispatch_semaphore_t dispatch_semaphore_create(long value)
/*!
* @discussion
* 信號量減1,如果結(jié)果小于0,那么等待隊列中信號增量到來直到timeout
* @param dsema
* 信號量
* @param timeout
* 等待時間
* 類型為dispatch_time_t,這里有兩個宏DISPATCH_TIME_NOW、DISPATCH_TIME_FOREVER
* @result
* 若等待成功返回0,timeout返回非0
*/
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
/*!
* @discussion
* 信號量加1,如果之前的信號量小于0,將喚醒一條等待線程
* @param dsema
* 信號量
* @result
* 喚醒一條線程返回非0,否則返回0
*/
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
OK,了解完3個函數(shù)都是干嘛用的,來試試水
超時,線程喚醒
- (void)semaphore {
dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
long a = dispatch_semaphore_wait(dsema, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
NSLog(@"a--%ld", a);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
long b = dispatch_semaphore_signal(dsema);
NSLog(@"b--%ld", b);
});
}

線程未喚醒,未超時
- (void)semaphore {
dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
long a = dispatch_semaphore_wait(dsema, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
NSLog(@"a--%ld", a);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
long b = dispatch_semaphore_signal(dsema);
NSLog(@"b--%ld", b);
});
}

結(jié)果和想的一樣,沒啥可繼續(xù)嘮的。多一嘴,線程喚醒與否和是否超時沒必然關(guān)系,要看代碼怎么寫。
OSSpinLock
OSSpinLock自旋鎖,使用時需導(dǎo)入頭文件#import <libkern/OSAtomic.h>
// 初始化 unlock為0,lock為非0
OSSpinLock spinLock = OS_SPINLOCK_INIT;
// 加鎖
OSSpinLockLock(&spinLock);
// 解鎖
OSSpinLockUnlock(&spinLock);
// 嘗試加鎖
BOOL b = OSSpinLockTry(&spinLock);
- (void)OSSpinLock {
OSSpinLock spinLock = OS_SPINLOCK_INIT;
NSLog(@"加鎖前:%zd", spinLock);
OSSpinLockLock(&spinLock);
NSLog(@"加鎖后:%zd", spinLock);
OSSpinLockUnlock(&spinLock);
NSLog(@"解鎖后:%zd", spinLock);
}

再來看一張截圖

OSSpinLock is deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead
由于自旋鎖存在優(yōu)先級反轉(zhuǎn)問題(可查看YYKit作者的這篇文章 不再安全的 OSSpinLock),在iOS 10.0中被<os/lock.h>中的os_unfair_lock()取代
os_unfair_lock
os_unfair_lock iOS 10.0新推出的鎖,用于解決OSSpinLock優(yōu)先級反轉(zhuǎn)問題
// 初始化
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
// 加鎖
os_unfair_lock_lock(unfairLock);
// 解鎖
os_unfair_lock_unlock(unfairLock);
// 嘗試加鎖
BOOL b = os_unfair_lock_trylock(unfairLock);
POSIX LOCK
POSIX LOCK為C語言級別的鎖,需引入頭像文件#import<pthread.h>
線程安全示例代碼:
static pthread_mutex_t lock;
- (void)pLock {
pthread_mutex_init(&lock, NULL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
pthread_mutex_lock(&lock);
_info = @"a";
NSLog(@"A--info:%@", _info);
pthread_mutex_unlock(&lock);
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
pthread_mutex_lock(&lock);
_info = @"b";
NSLog(@"B--info:%@", _info);
pthread_mutex_unlock(&lock);
}
});
}
POSIX LOCK不單有pthread_mutex_t還有pthread_cond_t等,因為不常用這里不做過多介紹。
NSDistributedLock
NSDistributedLock分布式鎖,用于MAC OS開發(fā),醬油路過
死鎖
所謂死鎖: 是指兩個或兩個以上的進程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進程稱為死鎖進程。
雖然進程在運行過程中,可能發(fā)生死鎖,但死鎖的發(fā)生也必須具備一定的條件,死鎖的發(fā)生必須具備以下四個必要條件。
1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內(nèi)某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。
2)請求和保持條件:指進程已經(jīng)保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。
3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
4)環(huán)路等待條件:指在發(fā)生死鎖時,必然存在一個進程——資源的環(huán)形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。 【引自百科】