本文主要介紹常見的鎖,以及synchronized、NSLock、遞歸鎖、條件鎖的底層分析
鎖
借鑒一張鎖的性能數(shù)據(jù)對比圖,如下所示
可以看出,圖中鎖的性能從高到底依次是:OSSpinLock(自旋鎖) -> dispatch_semaphone(信號量) -> pthread_mutex(互斥鎖) -> NSLock(互斥鎖) -> NSCondition(條件鎖) -> pthread_mutex(recursive 互斥遞歸鎖) -> NSRecursiveLock(遞歸鎖) -> NSConditionLock(條件鎖) -> synchronized(互斥鎖)
圖中鎖大致分為以下幾類:
-
【1、
自旋鎖】
:在自旋鎖中,線程會反復(fù)檢查變量是否可用
。由于線程這個(gè)過程中一直保持執(zhí)行,所以是一種忙等待
。 一旦獲取了自旋鎖,線程就會一直保持該鎖
,直到顯式釋放自旋鎖。自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對于線程只會阻塞很短時(shí)間的場合
是有效
的。對于iOS屬性的修飾符atomic
,自帶一把自旋鎖OSSpinLock
atomic
-
【2、
互斥鎖
】:互斥鎖
是一種用于多線程編程
中,防止兩條線程同時(shí)對同一公共資源(例如全局變量)進(jìn)行讀寫的機(jī)制
,該目的是通過將代碼切成一個(gè)個(gè)臨界區(qū)
而達(dá)成@synchronized
NSLock
pthread_mutex
-
【3、
條件鎖
】:條件鎖
就是條件變量
,當(dāng)進(jìn)程的某些資源要求不滿足
時(shí)就進(jìn)入休眠
,即鎖住了,當(dāng)資源被分配到
了,條件鎖打開
了,進(jìn)程繼續(xù)運(yùn)行NSCondition
NSConditionLock
-
【4、
遞歸鎖
】:遞歸鎖就是同一個(gè)線程可以加鎖N次而不會引發(fā)死鎖
。遞歸鎖是特殊的互斥鎖
,即是帶有遞歸性質(zhì)的互斥鎖
pthread_mutex(recursive)
NSRecursiveLock
-
【5、
信號量
】:信號量是一種更高級的同步機(jī)制
,互斥鎖
可以說是semaphore在僅取值0/1時(shí)的特例
,信號量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步
,而不單單是線程間互斥- dispatch_semaphore
-
【6、
讀寫鎖
】:讀寫鎖實(shí)際是一種特殊的自旋鎖
。將對共享資源的訪問分成讀者
和寫者
,讀者
只對共享資源進(jìn)行讀訪問
,寫者
則需要對共享資源進(jìn)行寫操作
。這種鎖相對于自旋鎖而言,能提高并發(fā)性
一個(gè)讀寫鎖同時(shí)只能有一個(gè)寫者或者多個(gè)讀者
,但不能既有讀者又有寫者,在讀寫鎖保持期間也是搶占失效的如果
讀寫鎖當(dāng)前沒有讀者,也沒有寫者
,那么寫者可以立刻獲得
讀寫鎖,否則它必須自旋
在那里, 直到?jīng)]有任何寫者或讀者。如果讀寫鎖沒有寫者,那么讀者可以立刻獲得讀寫鎖
其實(shí)基本的鎖
就包括三類:自旋鎖、互斥鎖、讀寫鎖
,其他的比如條件鎖、遞歸鎖、信號量
都是上層的封裝和實(shí)現(xiàn)
。
1、OSSpinLock(自旋鎖)
自從OSSpinLock
出現(xiàn)安全問題,在iOS10之后就被廢棄了。自旋鎖之所以不安全,是因?yàn)?code>獲取鎖后,線程會一直處于忙等待,造成了任務(wù)的優(yōu)先級反轉(zhuǎn)
。
其中的忙等待機(jī)制可能會造成高優(yōu)先級任務(wù)一直running等待
,占用時(shí)間片,而低優(yōu)先級的任務(wù)無法搶占時(shí)間片
,會造成一直不能完成,鎖未釋放的情況
在OSSpinLock
被棄用后,其替代方案是內(nèi)部封裝了os_unfair_lock
,而os_unfair_lock
在加鎖時(shí)會處于休眠狀態(tài)
,而不是自旋鎖的忙等狀態(tài)
2、atomic(原子鎖)
atomic
適用于OC中屬性的修飾符,其自帶一把自旋鎖,但是這個(gè)一般基本不使用,都是使用的nonatomic
在前面的文章中,我們提及setter
方法會根據(jù)修飾符調(diào)用不同方法,其中最后會統(tǒng)一調(diào)用reallySetProperty
方法,其中就有atomic
和非atomic
的操作
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
...
id *slot = (id*) ((char*)self + offset);
...
if (!atomic) {//未加鎖
oldValue = *slot;
*slot = newValue;
} else {//加鎖
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
...
}
從源碼中可以看出,對于atomic
修飾的屬性,進(jìn)行了spinlock_t
加鎖處理,但是在前文中提到OSSpinLock
已經(jīng)廢棄了,這里的spinlock_t
在底層是通過os_unfair_lock
替代了OSSpinLock
實(shí)現(xiàn)的加鎖。同時(shí)為了防止哈希沖突
,還是用了加鹽
操作
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
...
}
getter
方法中對atomic的處理,同setter是大致相同的
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();//解鎖
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
3、synchronized(互斥遞歸鎖)
-
開啟匯編調(diào)試,發(fā)現(xiàn)
@synchronized
在執(zhí)行過程中,會走底層的objc_sync_enter
和objc_sync_exit
方法
也可以通過clang
,查看底層編譯代碼
通過對objc_sync_enter
方法符號斷點(diǎn),查看底層所在的源碼庫,通過斷點(diǎn)發(fā)現(xiàn)在objc源碼中,即libobjc.A.dylib
objc_sync_enter & objc_sync_exit 分析
- 進(jìn)入
objc_sync_enter
源碼實(shí)現(xiàn)- 如果obj存在,則通過
id2data
方法獲取相應(yīng)的SyncData
,對threadCount、lockCount
進(jìn)行遞增
操作 - 如果obj不存在,則調(diào)用
objc_sync_nil
,通過符號斷點(diǎn)得知,這個(gè)方法里面什么都沒做,直接return了
- 如果obj存在,則通過
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//傳入不為nil
SyncData* data = id2data(obj, ACQUIRE);//重點(diǎn)
ASSERT(data);
data->mutex.lock();//加鎖
} else {//傳入nil
// @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;
}
- 進(jìn)入
objc_sync_exit
源碼實(shí)現(xiàn)- 如果obj存在,則調(diào)用
id2data
方法獲取對應(yīng)的SyncData,對threadCount、lockCount
進(jìn)行遞減
操作 - 如果obj為
nil
,什么也不做
- 如果obj存在,則調(diào)用
// End synchronizing on 'obj'. 結(jié)束對“ obj”的同步
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//obj不為nil
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();//解鎖
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {//obj為nil時(shí),什么也不做
// @synchronized(nil) does nothing
}
return result;
}
通過上面兩個(gè)實(shí)現(xiàn)邏輯的對比,發(fā)現(xiàn)它們有一個(gè)共同點(diǎn),在obj存在時(shí),都會通過id2data
方法,獲取SyncData
- 進(jìn)入
SyncData
的定義,是一個(gè)結(jié)構(gòu)體
,主要用來表示一個(gè)線程data
,類似于鏈表結(jié)構(gòu)
,有next指向,且封裝了recursive_mutex_t
屬性,可以確認(rèn)@synchronized
確實(shí)是一個(gè)遞歸互斥鎖
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;//類似鏈表結(jié)構(gòu)
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;//遞歸鎖
} SyncData;
- 進(jìn)入
SyncCache
的定義,也是一個(gè)結(jié)構(gòu)體,用于存儲線程,其中list[0]
表示當(dāng)前線程的鏈表data
,主要用于存儲SyncData
和lockCount
typedef struct {
SyncData *data;
unsigned int lockCount; // number of times THIS THREAD locked this block
} SyncCacheItem;
typedef struct SyncCache {
unsigned int allocated;
unsigned int used;
SyncCacheItem list[0];
} SyncCache;
id2data 分析
- 進(jìn)入
id2data
源碼,從上面的分析,可以看出,這個(gè)方法是加鎖和解鎖
都復(fù)用的方法
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS //tls(Thread Local Storage,本地局部的線程緩存)
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
//通過KVC方式對線程進(jìn)行獲取 線程綁定的data
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
//如果線程緩存中有data,執(zhí)行if流程
if (data) {
fastCacheOccupied = YES;
//如果在線程空間找到了data
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//通過KVC獲取lockCount,lockCount用來記錄 被鎖了幾次,即 該鎖可嵌套
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
//objc_sync_enter走這里,傳入的是ACQUIRE -- 獲取
lockCount++;//通過lockCount判斷被鎖了幾次,即表示 可重入(遞歸鎖如果可重入,會死鎖)
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);//設(shè)置
break;
}
case RELEASE:
//objc_sync_exit走這里,傳入的why是RELEASE -- 釋放
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);//判斷緩存中是否有該線程
//如果cache中有,方式與線程緩存一致
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {//遍歷總表
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE://加鎖
item->lockCount++;
break;
case RELEASE://解鎖
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache 從cache中清除使用標(biāo)記
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
//第一次進(jìn)來,所有緩存都找不到
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {//cache中已經(jīng)找到
if ( p->object == object ) {//如果不等于空,且與object相似
result = p;//賦值
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);//對threadCount進(jìn)行++
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object 沒有與當(dāng)前對象關(guān)聯(lián)的SyncData
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it 第一次進(jìn)來,沒有找到
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));//創(chuàng)建賦值
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) { //判斷是否支持棧存緩存,支持則通過KVC形式賦值 存入tls
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);//lockCount = 1
} else
#endif
{
// Save in thread cache 緩存中存一份
if (!cache) cache = fetch_cache(YES);//第一次存儲時(shí),對線程進(jìn)行了綁定
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
-
【第一步】首先在
tls
即線程緩存
中查找。在
tls_get_direct
方法中以線程為key
,通過KVC
的方式獲取與之綁定的SyncData
,即線程data。其中的tls
(),表示本地局部的線程緩存
,判斷獲取的data是否存在,以及判斷data中是否能找到對應(yīng)的
object
如果都找到了,在
tls_get_direct
方法中以KVC的方式獲取lockCount
,用來記錄對象被鎖了幾次
(即鎖的嵌套次數(shù))如果data中的
threadCount
小于等于0,或者lockCount
小于等于0時(shí),則直接崩潰-
通過傳入的
why
,判斷是操作類型如果是
ACQUIRE
,表示加鎖,則進(jìn)行lockCount++
,并保存到tls緩存如果是
RELEASE
,表示釋放,則進(jìn)行lockCount--
,并保存到tls緩存。如果lockCount
等于0
,從tls中移除
線程data如果是
CHECK
,則什么也不做
-
【第二步】如果tls中沒有,則在
cache緩存
中查找通過
fetch_cache
方法查找cache緩存中是否有線程如果有,則遍歷
cache總表
,讀取出線程對應(yīng)的SyncCacheItem
從
SyncCacheItem
中取出data
,然后后續(xù)步驟與tls的匹配是一致的
-
【第三步】如果cache中也沒有,即
第一次進(jìn)來
,則創(chuàng)建SyncData
,并存儲到相應(yīng)緩存中- 如果在cache中找到線程,且與object相等,則進(jìn)行
賦值
、以及threadCount++
- 如果在cache中沒有找到,則
threadCount
等于1
- 如果在cache中找到線程,且與object相等,則進(jìn)行
所以在id2data
方法中,主要分為三種情況
-
【第一次進(jìn)來,沒有鎖】:
threadCount = 1
lockCount = 1
存儲到
tls
-
【不是第一次進(jìn)來,且是同一個(gè)線程】
tls中有數(shù)據(jù),則
lockCount++
存儲到
tls
-
【不是第一次進(jìn)來,且是不同線程】
全局線程空間
進(jìn)行查找線程threadCount++
lockCount++
存儲到
cache
tls和cache表結(jié)構(gòu)
針對tls和cache緩存,底層的表結(jié)構(gòu)如下所示
哈希表
結(jié)構(gòu)中通過SyncList
結(jié)構(gòu)來組裝多線程
的情況SyncData
通過鏈表
的形式組裝當(dāng)前可重入
的情況下層通過
tls線程緩存、cache緩存
來進(jìn)行處理
底層主要有兩個(gè)東西:
lockCount、threadCount
,解決了遞歸互斥鎖,解決了嵌套可重入
@synchronized 坑點(diǎn)
下面代碼這樣寫,會有什么問題?
- (void)cjl_testSync{
_testArray = [NSMutableArray array];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (self.testArray) {
self.testArray = [NSMutableArray array];
}
});
}
}
運(yùn)行結(jié)果發(fā)現(xiàn),運(yùn)行就崩潰
崩潰的主要原因是testArray
在某一瞬間變成了nil,從@synchronized底層流程知道,如果加鎖的對象成了nil
,是鎖不住的,相當(dāng)于下面這種情況,block內(nèi)部不停的retain、release,會在某一瞬間上一個(gè)還未release,下一個(gè)已經(jīng)準(zhǔn)備release,這樣會導(dǎo)致野指針的產(chǎn)生
_testArray = [NSMutableArray array];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_testArray = [NSMutableArray array];
});
}
可以根據(jù)上面的代碼,打開edit scheme -> run -> Diagnostics
中勾選Zombie Objects
,來查看是否是僵尸對象,結(jié)果如下所示
我們一般使用@synchronized (self)
,主要是因?yàn)?code>_testArray的持有者是self
注意:野指針 vs 過渡釋放
野指針
:是指由于過渡釋放產(chǎn)生的指針還在進(jìn)行操作過渡釋放
:每次都會retain 和 release
總結(jié)
@synchronized
在底層封裝的是一把遞歸互斥鎖
@synchronized
的可重入,即可嵌套
,主要是由于lockCount
和threadCount
的搭配@synchronized
使用鏈表
的原因是鏈表方便下一個(gè)data的插入
,但是由于底層中
鏈表查詢、緩存的查找以及遞歸
,是非常耗內(nèi)存
以及性能
的,導(dǎo)致性能低
,所以在前文中,該鎖的排名在最后但是目前該鎖的使用頻率仍然很高,主要是因?yàn)?code>方便簡單,且不用解鎖
不能使用
非OC對象
作為加鎖對象,因?yàn)槠?code>object的參數(shù)為引用類型@synchronized (self)
這種適用于嵌套次數(shù)較少
的場景。這里鎖住的對象也并不永遠(yuǎn)是self
,這里需要讀者注意如果鎖嵌套次數(shù)較多,即
鎖self過多
,會導(dǎo)致底層的查找非常麻煩,因?yàn)槠涞讓邮擎湵磉M(jìn)行查找,所以會相對比較麻煩,所以此時(shí)可以使用NSLock、信號量
等
4、NSLock
NSLock
是對下層pthread_mutex的封裝
,使用如下
NSLock *lock = [[NSLock alloc] init];
[lock lock];
[lock unlock];
直接進(jìn)入NSLock
定義查看,其遵循了NSLocking
協(xié)議,下面來探索NSLock的底層實(shí)現(xiàn)
NSLock 底層分析
-
通過加符號斷點(diǎn)
lock
分析,發(fā)現(xiàn)其源碼在Foundation
框架中 -
由于OC的
Foundation
框架不開源,所以這里借助Swift
的開源框架Foundation
來 分析NSLock的底層實(shí)現(xiàn),其原理與OC是大致相同的通過源碼實(shí)現(xiàn)可以看出,底層是通過
pthread_mutex
互斥鎖實(shí)現(xiàn)的。并且在init方法中,還做了一些其他操作,所以在使用NSLock時(shí)需要使用init初始化
回到前文的性能圖中,可以看出NSLock
的性能僅次于 pthread_mutex
(互斥鎖),非常接近
使用弊端
請問下面block嵌套block的代碼中,會有什么問題?
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
testMethod(10);
});
}
-
在未加鎖之前,其中的current=9、10有很多條,導(dǎo)致數(shù)據(jù)混亂,主要原因是多線程導(dǎo)致的
如果像下面這樣加鎖,會有什么問題?
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
testMethod(10);
[lock unlock];
});
}
其運(yùn)行結(jié)果如下
會出現(xiàn)一直等待的情況,主要是因?yàn)?code>嵌套使用的遞歸,使用NSLock(簡單的互斥鎖,如果沒有回來,會一直睡覺等待)
,即會存在一直加lock,等不到unlock 的堵塞情況
所以,針對這種情況,可以使用以下方式解決
- 使用
@synchronized
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
@synchronized (self) {
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
}
};
testMethod(10);
});
}
- 使用遞歸鎖
NSRecursiveLock
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
[recursiveLock lock];
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
pthread_mutex
pthread_mutex
就是互斥鎖本身,當(dāng)鎖被占用,其他線程申請鎖時(shí),不會一直忙等待,而是阻塞線程并睡眠
使用
// 導(dǎo)入頭文件
#import <pthread.h>
// 全局聲明互斥鎖
pthread_mutex_t _lock;
// 初始化互斥鎖
pthread_mutex_init(&_lock, NULL);
// 加鎖
pthread_mutex_lock(&_lock);
// 這里做需要線程安全操作
// 解鎖
pthread_mutex_unlock(&_lock);
// 釋放鎖
pthread_mutex_destroy(&_lock);
6、NSRecursiveLock
NSRecursiveLock
在底層也是對pthread_mutex的封裝
,可以通過swift
的Foundation
源碼查看
對比NSLock
和 NSRecursiveLock
,其底層實(shí)現(xiàn)幾乎一模一樣,區(qū)別在于init時(shí),NSRecursiveLock
有一個(gè)標(biāo)識PTHREAD_MUTEX_RECURSIVE
,而NSLock
是默認(rèn)的
遞歸鎖
主要是用于解決一種嵌套形式
,其中循環(huán)嵌套居多
7、NSCondition
NSCondition
是一個(gè)條件鎖
,在日常開發(fā)中使用較少,與信號量有點(diǎn)相似:線程需要滿足條件才會往下走,否則會堵塞等待,直到條件滿足。經(jīng)典模型是生產(chǎn)消費(fèi)者模型
NSCondition的對象
實(shí)際上作為一個(gè)鎖
和 一個(gè)線程檢查器
鎖
主要 為了當(dāng)檢測條件時(shí)保護(hù)數(shù)據(jù)源,執(zhí)行條件引發(fā)的任務(wù)
線程檢查器
主要是根據(jù)條件決定是否繼續(xù)運(yùn)行線程
,即線程是否被阻塞
使用
//初始化
NSCondition *condition = [[NSCondition alloc] init]
//一般用于多線程同時(shí)訪問、修改同一個(gè)數(shù)據(jù)源,保證在同一 時(shí)間內(nèi)數(shù)據(jù)源只被訪問、修改一次,其他線程的命令需要在lock 外等待,只到 unlock ,才可訪問
[condition lock];
//與lock 同時(shí)使用
[condition unlock];
//讓當(dāng)前線程處于等待狀態(tài)
[condition wait];
//CPU發(fā)信號告訴線程不用在等待,可以繼續(xù)執(zhí)行
[condition signal];
底層分析
通過swift的Foundation源碼查看NSCondition
的底層實(shí)現(xiàn)
open class NSCondition: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
//初始化
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
}
//析構(gòu)
deinit {
pthread_mutex_destroy(mutex)
pthread_cond_destroy(cond)
mutex.deinitialize(count: 1)
cond.deinitialize(count: 1)
mutex.deallocate()
cond.deallocate()
}
//加鎖
open func lock() {
pthread_mutex_lock(mutex)
}
//解鎖
open func unlock() {
pthread_mutex_unlock(mutex)
}
//等待
open func wait() {
pthread_cond_wait(cond, mutex)
}
//等待
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
//信號,表示等待的可以執(zhí)行了
open func signal() {
pthread_cond_signal(cond)
}
//廣播
open func broadcast() {
// 匯編分析 - 猜 (多看多玩)
pthread_cond_broadcast(cond) // wait signal
}
open var name: String?
}
其底層也是對下層pthread_mutex
的封裝
NSCondition
是對mutex
和cond
的一種封裝(cond就是用于訪問和操作特定類型數(shù)據(jù)的指針)wait
操作會阻塞線程
,使其進(jìn)入休眠狀態(tài)
,直至超時(shí)signal
操作是喚醒
一個(gè)正在休眠等待的線程broadcast
會喚醒所有正在等待的線程
8、NSConditionLock
NSConditionLock
是條件鎖,一旦一個(gè)線程獲得鎖,其他線程一定等待
相比NSConditionLock
而言,NSCondition
使用比較麻煩,所以推薦使用NSConditionLock
,其使用如下
//初始化
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
//表示 conditionLock 期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內(nèi)部的 condition) 那它能執(zhí)行此行以下代碼,如果已經(jīng)有其他線程獲得鎖(可能是條件鎖,或者無條件 鎖),則等待,直至其他線程解鎖
[conditionLock lock];
//表示如果沒有其他線程獲得該鎖,但是該鎖內(nèi)部的 condition不等于A條件,它依然不能獲得鎖,仍然等待。如果內(nèi)部的condition等于A條件,并且 沒有其他線程獲得該鎖,則進(jìn)入代碼區(qū),同時(shí)設(shè)置它獲得該鎖,其他任何線程都將等待它代碼的 完成,直至它解鎖。
[conditionLock lockWhenCondition:A條件];
//表示釋放鎖,同時(shí)把內(nèi)部的condition設(shè)置為A條件
[conditionLock unlockWithCondition:A條件];
// 表示如果被鎖定(沒獲得 鎖),并超過該時(shí)間則不再阻塞線程。但是注意:返回的值是NO,它沒有改變鎖的狀態(tài),這個(gè)函 數(shù)的目的在于可以實(shí)現(xiàn)兩種狀態(tài)下的處理
return = [conditionLock lockWhenCondition:A條件 beforeDate:A時(shí)間];
//其中所謂的condition就是整數(shù),內(nèi)部通過整數(shù)比較條件
NSConditionLock
,其本質(zhì)就是NSCondition + Lock
,以下是其swift的底層實(shí)現(xiàn),
open class NSConditionLock : NSObject, NSLocking {
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenience override init() {
self.init(condition: 0)
}
public init(condition: Int) {
_value = condition
}
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func unlock() {
_cond.lock()
_thread = nil
_cond.broadcast()
_cond.unlock()
}
open var condition: Int {
return _value
}
open func lock(whenCondition condition: Int) {
let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
open func `try`() -> Bool {
return lock(before: Date.distantPast)
}
open func tryLock(whenCondition condition: Int) -> Bool {
return lock(whenCondition: condition, before: Date.distantPast)
}
open func unlock(withCondition condition: Int) {
_cond.lock()
_thread = nil
_value = condition
_cond.broadcast()
_cond.unlock()
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
_cond.lock()
while _thread != nil || _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open var name: String?
}
通過源碼可以看出
NSConditionLock
是NSCondition
的封裝NSConditionLock
可以設(shè)置鎖條件
,即condition值,而NSCondition
只是信號的通知
調(diào)試驗(yàn)證
以下面代碼為例,調(diào)試NSConditionLock
底層流程
- (void)cjl_testConditonLock{
// 信號量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; // conditoion = 1 內(nèi)部 Condition 匹配
// -[NSConditionLock lockWhenCondition: beforeDate:]
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"線程 2");
// self.myLock.value = 1;
[conditionLock unlockWithCondition:1]; // _value = 2 -> 1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
-
在
conditionLock
部分打上響應(yīng)斷點(diǎn),運(yùn)行(需要在真機(jī)
上運(yùn)行:模擬器上運(yùn)行的是Intel指令,而真機(jī)上運(yùn)行的是arm指令) 斷住,開啟匯編調(diào)試
register read
讀取寄存器,其中x0
是接收者self
,x1
是cmd
在
objc_msgSend
處加斷點(diǎn),再次讀寄存器 x0 --register read x0
,此時(shí)執(zhí)行到了[conditionLock lockWhenCondition:2];
讀x1,即
register read x1
,然后發(fā)現(xiàn)讀不出來,因?yàn)閤1存儲的是sel
,并不是對象類型,可以通過進(jìn)行強(qiáng)轉(zhuǎn)為SEL
讀取加符號斷點(diǎn)
-[NSConditionLock lockWhenCondition:]、-[NSConditionLock lockWhenCondition:beforeDate:]
,然后查看bl、b等跳轉(zhuǎn)-
讀取寄存器 x0、x2是當(dāng)前的
lockWhenCondition:beforeDate:
的參數(shù),實(shí)際走的是[conditionLock lockWhenCondition:1];
image -
通過匯編可知,
x2
移動到了x21
到這里后,我們調(diào)試的目的主要有兩個(gè):
NSCondition + lock
以及condition與value的值匹配
NSCondition + lock驗(yàn)證
-
繼續(xù)執(zhí)行,在bl處斷住,讀取寄存器
x0
,此時(shí)是跳轉(zhuǎn)至NSCondition
-
讀取 x1,即
po (SEL)0x00000001c746e484
所以可以驗(yàn)證NSConditionLock
在底層調(diào)用的是NSCondition
的lock
方法
condition與value的值匹配
-
繼續(xù)執(zhí)行,跳到
ldr
,即通過一個(gè)方法,拿到了 condition 2 的屬性值,存儲到x8
中register read x19
-
po (SEL)0x0000000283d0d220 -- x19的地址+0x10
register read x8,此時(shí)的x8中存儲的是 2
cmp x8, x21
,意思是將 x8和 x21匹配,即 2 和 1匹配,并不匹配
第二次來到cmp x8, x21
,此時(shí)的x8、x21 是匹配的 ,即[conditionLock lockWhenCondition:2];
-
此時(shí)是x8 和 x21 是匹配的,通過斷點(diǎn)也可以體現(xiàn)
demo分析匯總
線程 1
調(diào)用[NSConditionLock lockWhenCondition:]
,此時(shí)此刻因?yàn)椴粷M足當(dāng)前條件,所以會進(jìn)入 waiting 狀態(tài)
,當(dāng)前進(jìn)入到 waiting 時(shí),會釋放當(dāng)前的互斥鎖。此時(shí)當(dāng)前的
線程 3 調(diào)用[NSConditionLock lock:]
,本質(zhì)上是調(diào)用 [NSConditionLock lockBeforeDate:],這里不需要比對條件值
,所以線程 3 會打印接下來
線程 2 執(zhí)行[NSConditionLock lockWhenCondition:],
因?yàn)?code>滿足條件值,所以線程2 會打印,打印完成后會調(diào)用[NSConditionLock unlockWithCondition:],這個(gè)時(shí)候將value 設(shè)置為 1
,并發(fā)送 boradcast
, 此時(shí)線程 1 接收到當(dāng)前的信號,喚醒執(zhí)行并打印。自此當(dāng)前打印為
線程 3->線程 2 -> 線程 1
[NSConditionLock lockWhenCondition:];
這里會根據(jù)傳入的condition 值和 Value 值進(jìn)行對比
,如果不相等
,這里就會阻塞
,進(jìn)入線程池,否則的話就繼續(xù)代碼執(zhí)行[NSConditionLock unlockWithCondition:]: 這里會先更改當(dāng)前的 value 值
,然后進(jìn)行廣播
,喚醒當(dāng)前的線程
性能總結(jié)
-
OSSpinLock自旋鎖
由于安全性問題,在iOS10之后已經(jīng)被廢棄,其底層的實(shí)現(xiàn)用os_unfair_lock
替代使用
OSSpinLock
及所示,會處于忙等待狀態(tài)
而
os_unfair_lock
是處于休眠狀態(tài)
-
atomic原子鎖
自帶一把自旋鎖,只能保證setter、getter
時(shí)的線程安全,在日常開發(fā)中使用更多的還是nonatomic
修飾屬性atomic
:當(dāng)屬性在調(diào)用setter、getter
方法時(shí),會加上自旋鎖osspinlock
,用于保證同一時(shí)刻只能有一個(gè)線程調(diào)用屬性的讀或?qū)懀?code>避免了屬性讀寫不同步的問題。由于是底層編譯器自動生成的互斥鎖代碼,會導(dǎo)致效率相對較低nonatomic
:當(dāng)屬性在調(diào)用setter、getter方法時(shí),不會加上自旋鎖,即線程不安全
。由于編譯器不會自動生成互斥鎖代碼,可以提高效率
@synchronized
在底層維護(hù)了一個(gè)哈希表
進(jìn)行線程data的存儲,通過鏈表
表示可重入
(即嵌套)的特性,雖然性能較低,但由于簡單好用,使用頻率很高NSLock
、NSRecursiveLock
底層是對pthread_mutex
的封裝NSCondition
和NSConditionLock
是條件鎖,底層都是對pthread_mutex
的封裝,當(dāng)滿足某一個(gè)條件時(shí)才能進(jìn)行操作,和信號量dispatch_semaphore
類似
鎖的使用場景
如果只是
簡單
的使用,例如涉及線程安全,使用NSLock
即可在
循環(huán)嵌套
中,如果對遞歸鎖掌握的很好,則建議使用遞歸鎖
,因?yàn)樾阅芎?/p>如果是
循環(huán)嵌套
,并且還有多線程影響
時(shí),例如有等待、死鎖現(xiàn)象時(shí),建議使用@synchronized
(因?yàn)樵?code>synchronized中無論怎么重入,都沒有關(guān)系,而NSRecursiveLock
可能會出現(xiàn)崩潰現(xiàn)象)