1、八大鎖效率

- 八大鎖分別:
- 自璇所:OSSpinLock。在
iOS10以后該鎖被重寫(xiě),會(huì)在堵塞時(shí)進(jìn)行休眠; - 互斥鎖:NSLock、NScondition、NSRecursiceLock、NSConditionLock、@synchronize;以及更加偏底層:pthread_mutex、pthread_mutex(recursive);
- 自璇所:OSSpinLock。在
2、synchronize探索入口
所有底層的探索都需要一個(gè)切入點(diǎn),像這樣的代碼段除了堆棧的方式,還有clang、查看匯編的方式。
@synchronized (self) {
i += 1;
}
2.1 查看堆棧

事實(shí)證明在這個(gè)問(wèn)題上是不適用的;
2.2 匯編方式

- 可以看到使用了
@synchronize之后在方法塊前后調(diào)用了兩個(gè)方法objc_sync_enter、objc_sync_exit;
繼續(xù)增加objc_sync_enter的符號(hào)斷點(diǎn)之后;

-
@synchronize是屬于libobjc.A.dylib庫(kù)的; -
objc_sync_enter在底層callq(調(diào)用)函數(shù)id2data(objc_object*, usage);
2.3 clang方式

使用命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o vc.cpp
- 根據(jù)
clang獲取編譯后的代碼,也可以看到熟悉的兩個(gè)方法objc_sync_enter、objc_sync_exit,同時(shí)也驗(yàn)證了匯編方式的結(jié)論;
3、objc_sync_enter 源碼分析
通過(guò)符號(hào)斷點(diǎn),得知@synchronize是在我們熟悉的libobjc庫(kù)中,在我之前的文章中可以得到OC底層探索02- objc4-781 源碼編譯;
enum usage { ACQUIRE, RELEASE, CHECK };
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
// ACQUIRE 枚舉值
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;
}
BREAKPOINT_FUNCTION(
//其實(shí)什么都沒(méi)有做
void objc_sync_nil(void)
);
看到這段代碼之后再回頭看看
objc_sync_enter的匯編部分,是不是發(fā)現(xiàn)其實(shí)匯編也就那樣;
-
data->mutex.lock()這才是真正的加鎖操作,是系統(tǒng)recursive_mutex_t遞歸互斥鎖的更高層封裝; - 如果傳入的
obj是個(gè)空值,系統(tǒng)是沒(méi)有做任何事的,所以在使用時(shí)要保證標(biāo)示對(duì)象一定不能為空; - 通過(guò)異常判斷之后進(jìn)入函數(shù)
id2data(obj, ACQUIRE);
4、objc_sync_exit
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
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 {
// @synchronized(nil) does nothing
}
return result;
}
- 同樣也是進(jìn)入到了
id2data(obj, RELEASE)只是第二個(gè)參數(shù)不一樣。提現(xiàn)了無(wú)處不在的抽象和封裝思想;
5、id2data(obj, enum usage) 核心函數(shù)
代碼非常長(zhǎng),這里分為四步分來(lái)分析
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
// 包含當(dāng)前對(duì)象的鏈表
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
// 第一部分
}
#endif
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);
if (cache) {
// 第二部分
}
lockp->lock();
//第三部分
done:
lockp->unlock();
if (result) {
// 第四部分
}
return result;
}
3.3.1 第一部分 快速緩存
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
// 檢查當(dāng)前線程的快速緩存
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
// 標(biāo)示快速緩存被占用;防止后續(xù)該線程的其他鎖進(jìn)行替換,而導(dǎo)致的問(wèn)題;
fastCacheOccupied = YES;
// 快速緩存中找到該緩存對(duì)象
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
// lockCount標(biāo)記該鎖的加鎖次數(shù)
result = data;
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: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
// 緩存次數(shù)為0后,將快速緩存對(duì)象制空
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
// 原子性的對(duì)緩存對(duì)象的線程使用數(shù)減一
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
}
//找到處理完lockCount后直接返回
return result;
}
}
#endif
- 在沒(méi)有特別設(shè)置:
SUPPORT_DIRECT_THREAD_KEYS默認(rèn)為1; - 當(dāng)前緩存的快速緩存: 當(dāng)前線程
第一次加鎖的對(duì)象會(huì)被定義為快速緩存;(大多數(shù)情況下,一條線程只會(huì)使用一個(gè)標(biāo)示對(duì)象進(jìn)行加鎖); -
SYNC_DATA_DIRECT_KEY、SYNC_COUNT_DIRECT_KEY都是在當(dāng)前線程的局部緩存中查找緩存對(duì)象SyncData、緩存次數(shù)lockCount;
3.3.1 SyncData
在快速緩存階段,系統(tǒng)保存了結(jié)構(gòu)為SyncData的對(duì)象。
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // 使用該鎖的線程數(shù)
recursive_mutex_t mutex; // 遞歸互斥鎖
} SyncData;
-
SyncData鎖對(duì)象對(duì)象,是一個(gè)鏈表結(jié)構(gòu); -
SyncData將synchronize鎖所需要的數(shù)據(jù)進(jìn)行保存;
3.3.2 第二部分 慢速緩存
這一部分涉及到了慢速緩存,如果在快速緩存中沒(méi)有找到則會(huì)來(lái)到這部分;
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// 這部分和快速緩存操作基本一致
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) {
// 緩存數(shù)組的總個(gè)數(shù)減少
cache->list[i] = cache->list[--cache->used];
// 原子性操作
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
}
return result;
}
}
- 測(cè)試后發(fā)現(xiàn),慢速緩存也是從
當(dāng)前線程的進(jìn)行查找; - cache->list[i] = cache->list[--cache->used];
將數(shù)組最后一個(gè)對(duì)象移動(dòng)到當(dāng)前下標(biāo)位置,然后將數(shù)組進(jìn)行縮容; - 通過(guò)這個(gè)雙重緩存結(jié)構(gòu),提高了鎖對(duì)象
syncdata的查找效率;
3.3.2 SyncCache
在慢速緩存中出現(xiàn)了這樣一個(gè)結(jié)構(gòu)SyncCache.
typedef struct {
SyncData *data; // 鎖對(duì)象
unsigned int lockCount; // 緩存次數(shù)
} SyncCacheItem;
typedef struct SyncCache {
unsigned int allocated;
unsigned int used; // 緩存數(shù)組的個(gè)數(shù)
SyncCacheItem list[0]; // 鎖對(duì)象的列表
} SyncCache;
-
SyncCache是慢速緩存的實(shí)體體現(xiàn); -
SyncCacheItem包含了SyncData鎖對(duì)象以及該鎖對(duì)象的緩存次數(shù);
3.3.3 第三部分
在雙重緩存下都沒(méi)有命中后會(huì)來(lái)到這部分,這部分會(huì)在:初次加鎖、同一對(duì)象不同線程加鎖的時(shí)候進(jìn)入.
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
// listp在函數(shù)最開(kāi)始進(jìn)行的獲取
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
// 同一對(duì)象不同線程加鎖進(jìn)入這里
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
// 找到一個(gè)未使用的,使用它,(復(fù)用)
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// 全新創(chuàng)建
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
//保存到節(jié)點(diǎn)的第一個(gè)
result->nextData = *listp;
*listp = result;
-
同一對(duì)象不同線程加鎖時(shí)會(huì)進(jìn)行原子的threadCount++; - 由于list中SyncData不會(huì)進(jìn)行刪除,所以
需要復(fù)用; - 如果1、2步都沒(méi)有名字,則進(jìn)行
全新創(chuàng)建,并保存到節(jié)點(diǎn)的第一個(gè);
3.3.3 StripedMap
listp是在函數(shù)最開(kāi)始進(jìn)行獲取,鎖對(duì)象存儲(chǔ)結(jié)構(gòu)。通過(guò)對(duì)object的地址hash計(jì)算后確定數(shù)組下標(biāo);
SyncData **listp = &LIST_FOR_OBJ(object);
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
//哈希算法
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
}
- StripedMap在OC底層探索19-weak和assign區(qū)別淺談在分析weak存儲(chǔ)結(jié)構(gòu)時(shí)也出現(xiàn)過(guò),都是通過(guò)hash算法來(lái)進(jìn)行分組,減少數(shù)據(jù)查找的難度;
- 不同的是
weak的StripedMap對(duì)應(yīng)的是一張SideTable表;而@synchronized的StripedMap對(duì)應(yīng)的是一個(gè)鏈表結(jié)構(gòu);
synchronized結(jié)構(gòu)
3.3.4 第四部分 done
done:
lockp->unlock();
if (result) {
// 解鎖流程
if (why == RELEASE) {
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) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
// 否則保存到當(dāng)前線程的慢速緩存list中
{
// Save in thread cache
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
// 最終完成加、解鎖處理
return result;
- 在第三部分處理完之后都會(huì)來(lái)到done中;
-
快速緩存和慢速緩存會(huì)互斥存在;
總結(jié)
通過(guò)函數(shù)id2data的參數(shù)完成了加、解鎖操作。并且使用了快速緩存、慢速緩存雙重緩存,來(lái)提高synvData的命中速度。除此之外stiped+syncData鏈表對(duì)鎖實(shí)體進(jìn)行保存。利用threadCount+lockCount實(shí)現(xiàn)了多線程、重復(fù)加、解鎖操作;
通過(guò)這些操作提高了遞歸鎖的安全性,但是也降低了性能;
補(bǔ)充
線程局部存儲(chǔ)(Thread Local Storage,TLS):是操作系統(tǒng)為線程單獨(dú)提供的私有空間,通常只有有限的容量。Linux系統(tǒng)下通常通過(guò)pthread庫(kù)中的。
還有的幾種鎖,以后有機(jī)會(huì)在探索吧~畢竟大部分都在Founation庫(kù)中,不是很好分析。
歡迎在留言區(qū)和我溝通!
