為什么要用鎖?
為了保證多線程訪問一塊公共資源時(shí)虚倒,對資源的保護(hù)逝撬。或者說是多線程安全 or 線程同步
但是線程同步的實(shí)現(xiàn)并不是只有加鎖才能解決度宦,串行隊(duì)列也是一種解決方式踢匣。
鎖通用使用步驟
//帶?的是一定要有的步驟。
?初始化鎖 | 賦予一定參數(shù)
?加鎖 | 通過一定條件加鎖
等待 | 線程進(jìn)入 wait 等待條件
?處理公共資源代碼 { }
?解鎖 | 給鎖賦予條件
銷毀鎖 & 鎖的屬性
??????????????????????正片
????????????????????
1.OSSpinLock (Deprecated
)
介紹: 是一種'自旋鎖'
使用:
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
//加鎖
OSSpinLockLock(&lock);
//嘗試加鎖
BOOL lockStatus = OSSpinLockTry(&lock);
//你需要保護(hù)的操作
{}
//解鎖
OSSpinLockUnlock(&lock);
#define OS_SPINLOCK_INIT 0 (就是把lock賦值為 0)
不過這個(gè)鎖已經(jīng)被廢棄掉了戈抄±牖#可查看.h文件中的介紹。
'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead
廢棄原因:
新版 iOS 中划鸽,系統(tǒng)維護(hù)了 5 個(gè)不同的線程優(yōu)先級/QoS: background输莺,utility,default裸诽,user-initiated嫂用,user-interactive。高優(yōu)先級線程始終會在低優(yōu)先級線程前執(zhí)行丈冬,一個(gè)線程不會受到比它更低優(yōu)先級線程的干擾嘱函。這種線程調(diào)度算法會產(chǎn)生潛在的優(yōu)先級反轉(zhuǎn)問題,從而破壞了 spin lock埂蕊。
具體來說往弓,如果一個(gè)低優(yōu)先級的線程獲得鎖并訪問共享資源,這時(shí)一個(gè)高優(yōu)先級的線程也嘗試獲得這個(gè)鎖粒梦,它會處于 spin lock 的忙等狀態(tài)從而占用大量 CPU亮航。此時(shí)低優(yōu)先級線程無法與高優(yōu)先級線程爭奪 CPU 時(shí)間,從而導(dǎo)致任務(wù)遲遲完不成匀们、無法釋放 lock。這并不只是理論上的問題准给,libobjc 已經(jīng)遇到了很多次這個(gè)問題了泄朴,于是蘋果的工程師停用了 OSSpinLock。
but, 除非開發(fā)者能保證訪問鎖的線程全部都處于同一優(yōu)先級露氮,否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了祖灰。
2.os_unfair_lock
介紹: 是一種低級鎖('Low-level'),'互斥鎖' 畔规,看了好多博客說是自旋鎖局扶,其實(shí)都是錯(cuò)的。
os_unfair_lock雖然是 'OSSpinLock' 的替代品叁扫,但是它確實(shí)是互斥鎖三妈。
??有對os_unfair_lock是互斥鎖的考證。
.h中的官方解釋
Does not spin on contention but waits in the kernel to be woken up
by an unlock
使用方法
#import <os/lock.h>
//靜態(tài)初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加鎖
os_unfair_lock_lock(&lock);
bool isCanLock = os_unfair_lock_trylock(&lock);
//解鎖
os_unfair_lock_unlock(&lock);
3.pthread_mutex_t
介紹: 是一種跨平臺的鎖(Linux,Unix,OS,iOS)莫绣,本質(zhì)上是一種 互斥鎖畴蒲,可以動態(tài)初始化。
根據(jù)傳入的參數(shù)生成對應(yīng)的鎖.(e.g. 遞歸鎖)
使用介紹
#import <pthread.h>
//靜態(tài)初始化鎖
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
//動態(tài)初始化
pthread_mutex_t mutex;
//初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
//傳入 PTHREAD_MUTEX_RECURSIVE (遞歸鎖屬性对室。)
//PTHREAD_MUTEX_ERRORCHECK(錯(cuò)誤檢查)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&mutex, NULL); (初始化屬性可為null)
//注: # define NULL ((void*)0)
//動態(tài)初始化鎖
pthread_mutex_init(&mutex, &attr);
//銷毀模燥,一定銷毀對應(yīng)的屬性咖祭。
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
//加鎖解鎖
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
// 關(guān)于另一種屬性的解釋 PTHREAD_MUTEX_ERRORCHECK
This type of mutex provides error checking. A thread attempting to relock
this mutex without first unlocking it shall return with an error. A thread
attempting to unlock a mutex which another thread has locked shall return
with an error. A thread attempting to unlock an unlocked mutex shall
return with an error.
4.pthread_cond_t
介紹:條件鎖,是pthread_mutex_t引申出來的鎖蔫骂。
配合pthread_mutex_t來一起使用么翰,可以用于線程的同步。亦或者是解決線程間的依賴關(guān)系辽旋。
當(dāng)當(dāng)前線程進(jìn)入 wait 之后硬鞍, 當(dāng)前線程 mutex 會放開,保證其他線程可以拿到鎖 mutex 執(zhí)行戴已,
直到收到 signal 信號或者broadcast之后才會喚醒 當(dāng)前線程固该,并且 喚醒后再次對 mutex 進(jìn)行加鎖。
//條件鎖
pthread_cond_t cond;
//靜態(tài)初始化
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
pthread_condattr_t condAttr;
//初始化attr參數(shù)
pthread_condattr_init(&condAttr);
//動態(tài)初始化糖儡,也可不傳attr參數(shù)
pthread_cond_init(&cond, &condAttr);
pthread_cond_init(&cond, NULL);
//1.放開當(dāng)前鎖 2.使當(dāng)前線程進(jìn)入休眠(wait) 3.喚醒后會再次mutex程加鎖
pthread_cond_wait(&cond, &mutex);
//在time之前等待伐坏,之后放開鎖。
pthread_cond_timedwait(&cond, &mutex, const struct timespec *restrict _Nullable);
//喚醒一個(gè)被wait的線程
pthread_cond_signal(&cond);
//喚醒所有被wait的線程
pthread_cond_broadcast(&cond);
//銷毀attr 和cond
pthread_condattr_destroy(&condAttr);
pthread_cond_destroy(&cond);
5.pthread_rwlock_t
介紹: 讀寫鎖,(互斥鎖的進(jìn)化)分為讀鎖(rlock)和寫鎖(wlock),可以有多個(gè)線程共同持有讀鎖握联,但是寫鎖只能有一個(gè)線程持有桦沉,如果讀鎖被持有是,寫鎖是不能持有的金闽。
需要等待讀鎖unlock 才能持有寫鎖,同樣需要寫鎖unlock才能持有讀鎖纯露。
具體使用
//靜態(tài)初始化
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
_rwlock = lock;
//動態(tài)初始化
pthread_rwlockattr_init(&_rwlock_attr);
pthread_rwlock_init(&_rwlock, &_rwlock_attr);
- (void)__add {
//寫鎖上鎖
pthread_rwlock_wrlock(&_rwlock);
[super __add];
pthread_rwlock_unlock(&_rwlock);
}
- (void)__readArr {
//讀鎖上鎖
pthread_rwlock_rdlock(&_rwlock);
NSLog(@"self.lockArr=%@",self.lockArray);
pthread_rwlock_unlock(&_rwlock);
}
- (void)dealloc {
//銷毀 鎖 & 鎖的屬性
pthread_rwlockattr_destroy(&_rwlock_attr);
pthread_rwlock_destroy(&_rwlock);
}
/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
6.NSLock 、NSCondition 代芜、NSConditionLock和NSRecursiveLock
簡介: 都屬于互斥鎖埠褪。
NSLock 底層是對 pthread_mutex_t 的封裝.對應(yīng)的參數(shù)是 PTHREAD_MUTEX_NORMAL
NSCondition 底層則是對 pthread_cond_t 的封裝.
NSConditionLock 的底層則是使 NSCondition 實(shí)現(xiàn)的.
NSRecursiveLock 則是對 pthread_mutex_t 的 PTHREAD_MUTEX_RECURSIVE 參數(shù)的封裝。
實(shí)現(xiàn)原理可以通過 GNUstep 查看
以上都是蘋果對pthread_mutex的封裝挤庇,讓鎖的使用更面向?qū)ο罅恕?
具體使用
NSLock *lock = [[NSLock alloc] init];
//嘗試加鎖
BOOL isLocked = [lock tryLock];
[lock lock];
[lock unlock];
//由于 NSCondition 是對 pthread_cond_t 的封裝钞速,所以使用方法與 pthread_cond_t 基本一致。
//不同的是不需要我們?nèi)ナ謩愉N毀鎖嫡秕。
NSCondition *conLock = [[NSCondition alloc] init];
[conLock lock];
[conLock wait];
[conLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
[conLock unlock];
[conLock signal];
[conLock broadcast];
//NSConditionLock 設(shè)置condition 保證多線程中的同步渴语,按自己想要的順序執(zhí)行。
//先add 然后 remove昆咽。
self.conditionLock = [[NSConditionLock alloc] init]; //默認(rèn)condition 是0驾凶。
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
- (void)demoTest {
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
sleep(3);
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
- (void)__remove {
[self.conditionLock lockWhenCondition:2];
[super __remove];
[self.conditionLock unlock];
}
- (void)__add {
[self.conditionLock lockWhenCondition:1];
[super __add];
[self.conditionLock unlockWithCondition:2];
}
// NSRecursiveLock 用法類似于 NSLock 但是可以遞歸加鎖。
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
[recursiveLock lock];
[recursiveLock unlock];
7.dispatch_semaphore
簡單來說并不是鎖掷酗,而是通過信號的方式调违,可以實(shí)現(xiàn)鎖的一種機(jī)制。
簡單使用
//create 的value 代表最多有幾個(gè)信號量
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_after(dispatch_time( DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"走到了塊里");
dispatch_semaphore_signal(sema);//發(fā)送1個(gè)信號量
});
NSLog(@"等待-----");
//如果信號量的值 >0,就讓信號量的值減1汇在,然后繼續(xù)往下執(zhí)行代碼
//如果信號量的值 <= 0,就讓線程 `sleep` (休眠).直到信號量 >0.
dispatch_wait(sema, DISPATCH_TIME_FOREVER);
// dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); //兩個(gè)方法都可
NSLog(@"完成”);
2018-07-13 16:34:17.524572 AddressBook[6830:473890] 等待-----
2018-07-13 16:34:20.823293 AddressBook[6830:473911] 走到了塊里
2018-07-13 16:34:20.823515 AddressBook[6830:473890] 完成
8.@synchronized(id obj) { }
簡介: 互斥鎖
關(guān)于 更深的synchronized的實(shí)現(xiàn)。實(shí)際上也是對 pthread_mutex 的遞歸鎖的一個(gè)封裝糕殉。
簡單使用和底層實(shí)現(xiàn):
@synchronized(id obj) {
//公共資源操作
NSLog(@"加鎖");
}
實(shí)現(xiàn)原理: 調(diào)用堆棧
0x107d25111 <+2193>: callq 0x107d27b68 ; symbol stub for: objc_sync_enter
0x107d25139 <+2233>: callq 0x107d27ab4 ; symbol stub for: NSLog
0x107d2514a <+2250>: callq 0x107d27b6e ; symbol stub for: objc_sync_exit
0x107d2515c <+2268>: callq 0x107d27b44 ; symbol stub for: objc_release
通過查看 objc4-723 中 objc-sync.mm 源碼亩鬼,可以知道:
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
SyncData結(jié)構(gòu)體如下
typedef struct SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
objc_sync_enter 中 通過 synchronized 傳入的對象obj 生成 data 結(jié)構(gòu)體指針
然后data 在 LIST_FOR_OBJ(obj) (static StripedMap<SyncList> sDataLists)中
取出對應(yīng)的 mutex.lock.
這個(gè)obj 就作為這個(gè)鎖的key殖告,從對應(yīng)的hash表中找到對應(yīng)的鎖。
只要傳入的obj相同雳锋,對應(yīng)的鎖就相同黄绩。
如果傳入nil 則 // @synchronized(nil) does nothing (什么也做)
mutex 對應(yīng)的就是 recursive_mutex_t。
通過源碼再往里面查看就知道 synchronized 實(shí)質(zhì)就是 一把 RECURSIVE 的pthread_mutex_t (遞歸鎖)玷过。
lockdebug_recursive_mutex_lock(recursive_mutex_t *lock)
{
auto& locks = ownedLocks();
setLock(locks, lock, RECURSIVE);
}
補(bǔ)充 atomic (原子性) 很好的參考博客
改變setter爽丹,getter方法的實(shí)現(xiàn),對方法進(jìn)行加鎖和解鎖的操作(原子性操作)辛蚊。
保證 setter和getter方法內(nèi)部線程同步粤蝎。底層實(shí)現(xiàn)是 os_unfair_lock 。
但是: 并不能保證 使用atomic修飾的屬性 的線程安全袋马。
而且性能消耗太大, 因?yàn)?setter和getter 方法調(diào)用頻率太高3跖臁!
源碼實(shí)現(xiàn):
objc4-723 中全局搜索atomic 發(fā)現(xiàn)在 objc-abi.h 文件中的
objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset,
id _Nullable newValue, BOOL atomic, signed char shouldCopy)
方法中虑凛。通過調(diào)用棧查看具體實(shí)現(xiàn):
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
} //省略部分代碼
由 reallySetProperty 方法可知碑宴,如果是atomic 則會在 set 前生成 PropertyLocks 鎖。
set 值之后 解鎖
對應(yīng)的 getter 方法中 的實(shí)現(xiàn)
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
//再通過源碼查看
StripedMap<spinlock_t> PropertyLocks;
slotlock是PropertyLocks通過 slot 從StripedMap 獲取桑谍。
在查看 slotlock定義
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
}
可見 底層是通過 os_unfair_lock 實(shí)現(xiàn)延柠。
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
幾種主要鎖的類別
互斥鎖 sleep
是一種 low-level 的鎖,相對于自旋鎖來說比較低級锣披。如果發(fā)現(xiàn)沒有持有鎖贞间,則使線程進(jìn)入sleep 狀態(tài)。
自旋鎖 busy-wait
相當(dāng)于是一個(gè)外部死循環(huán)盈罐。當(dāng)其他線程訪問被鎖的資源后榜跌,會一直進(jìn)行循環(huán),進(jìn)入 busy-wait的狀態(tài)盅粪,自旋鎖不會引起調(diào)用者休眠,節(jié)省了線程休眠的狀態(tài)切換悄蕾,所以有更高的效率票顾。
直到其他線程鎖放開,因?yàn)榫€程一直在進(jìn)行執(zhí)行帆调,所以會一直占用cpu資源奠骄。
遞歸鎖
可以讓當(dāng)前線程遞歸的去給當(dāng)前線程加鎖,然后解鎖番刊。
比如:
//動態(tài)初始化
pthread_mutex_t mutex;
//初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);
- (void)pthread_mutex_recursive {
//加鎖
pthread_mutex_lock(&mutex);
for (int i=0; i<5; i++) {
//遞歸調(diào)用
[self pthread_mutex_recursive];
}
pthread_mutex_unlock(&mutex);
}
注意點(diǎn)
使用任何鎖都需要消耗系統(tǒng)資源(內(nèi)存資源和CPU時(shí)間)含鳞,這種資源消耗可以分為兩類:
1.建立鎖所需要的資源
2.當(dāng)線程被阻塞時(shí)所需要的資源
同步方案的性能排序(待考證) 從高到低
- os_unfair_lock
- OSSpinLock (Deprecated)
- dispatch_semaphore
- pthread_mutex_t
- dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL); (本文未做詳細(xì)介紹)
- NSLock
- NSCondition
- pthread_mutex( recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
為什么 @synchronized 執(zhí)行效率比 NSLock效率低?
鎖的粒度: @synchronized 的鎖粒度更粗芹务,它鎖定的是一個(gè)對象蝉绷,而不是一個(gè)具體的代碼塊鸭廷。這意味著如果多個(gè)臨界區(qū)使用了相同的對象作為鎖,那么它們將會互斥熔吗,即使它們沒有直接的競爭條件辆床。相比之下,NSLock 允許更細(xì)粒度的控制桅狠,可以只鎖定需要保護(hù)的臨界區(qū)讼载,從而減少了互斥的范圍,提高了并發(fā)性能中跌。
互斥鎖的開銷: @synchronized 在內(nèi)部使用了互斥鎖來實(shí)現(xiàn)線程同步咨堤,而 NSLock 也使用了類似的機(jī)制。然而漩符,由于 @synchronized 是一個(gè)語言級別的特性一喘,其實(shí)現(xiàn)可能比 NSLock 更復(fù)雜,并且可能會引入一些額外的開銷陨仅,如鎖的創(chuàng)建和釋放等津滞。
底層實(shí)現(xiàn)差異: @synchronized 的底層實(shí)現(xiàn)可能會依賴于運(yùn)行時(shí)系統(tǒng)的特性,而 NSLock 則是直接調(diào)用系統(tǒng)提供的互斥鎖機(jī)制灼伤。因此触徐,NSLock 的實(shí)現(xiàn)可能更接近操作系統(tǒng)的底層機(jī)制,性能上可能會更高效一些狐赡。
關(guān)于 os_unfair_lock 是互斥鎖的考證
thread_9中對資源加鎖撞鹉,在thread_10中對os_unfair_lock_lock()的實(shí)現(xiàn)進(jìn)行disassembly 查看。
下面是調(diào)用棧颖侄,省略了其他步驟
-> 0x10d7628c4 <+20>: movq 0x43bd(%rip), %rdi ; OSUnfairLock._unfair_lock
-> 0x10d762fb6 <+0>: jmpq *0x217c(%rip); os_unfair_lock_lock
-> 0x1128d334b <+19>: jmp 0x1128d3350; _os_unfair_lock_lock_slow
-> 0x1128d33cd <+125>: callq 0x1128d3ae6 ; _os_ulock_wait
-> 0x1128d3afa <+20>: callq 0x1128d5318 ; symbol stub for: __ulock_wait
-> 0x1128d5318 <+0>: jmpq *0x1d5a(%rip); __ulock_wait
-> 0x1128ae31c <+8>: syscall
當(dāng)調(diào)用玩 syscall的時(shí)候線程進(jìn)入休眠而不是進(jìn)行自旋鸟雏。所以 os_unfair_lock是互斥鎖
#0 0x00000001128ae31e in __ulock_wait ()
用到的資源
寫在最后: 關(guān)于技術(shù)的運(yùn)用,總結(jié)一句話:知識決定你的下限览祖,但是想象力決定你的上限孝鹊。熟練的運(yùn)用在項(xiàng)目中才是我們最需要的。
可以關(guān)注 我的掘金 也可以 關(guān)注 我的簡書
如果本文幫助了你展蒂,也可以贊助我一哈又活,O(∩_∩)O哈哈~