一挟鸠、性能分析
網(wǎng)上很多對比八大鎖性能的文章,時間大部分比較早樱调。蘋果對某些鎖內(nèi)部進(jìn)行了優(yōu)化惜傲。這篇文章找中會以10
萬次數(shù)據(jù)做對比對主流鎖性能進(jìn)行分析耘戚。
1.1 調(diào)用情況模擬
OSSpinLock
OSSpinLock
在iOS 10
以后廢棄了,不過還可以調(diào)用操漠。需要導(dǎo)入頭文件<libkern/OSAtomic.h>
:
int hp_runTimes = 100000;
/** OSSpinLock 性能 */
{
OSSpinLock hp_spinlock = OS_SPINLOCK_INIT;
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
OSSpinLockLock(&hp_spinlock);//解鎖
OSSpinLockUnlock(&hp_spinlock);
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("OSSpinLock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
dispatch_semaphore_t
信號量是GCD
提供的:
/** dispatch_semaphore_t 性能 */
{
dispatch_semaphore_t hp_sem = dispatch_semaphore_create(1);
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
dispatch_semaphore_wait(hp_sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(hp_sem);
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("dispatch_semaphore_t: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
os_unfair_lock
os_unfair_lock
是iOS10
推出的新類型的鎖需要導(dǎo)入頭文件<os/lock.h>
:
/** os_unfair_lock_lock 性能 */
{
os_unfair_lock hp_unfairlock = OS_UNFAIR_LOCK_INIT;
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
os_unfair_lock_lock(&hp_unfairlock);
os_unfair_lock_unlock(&hp_unfairlock);
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent() ;
printf("os_unfair_lock_lock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
pthread_mutex_t
pthread_mutex_t
是linux
下提供的鎖,需要導(dǎo)入頭文件<pthread/pthread.h>
:
/** pthread_mutex_t 性能 */
{
pthread_mutex_t hp_metext = PTHREAD_MUTEX_INITIALIZER;
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
pthread_mutex_lock(&hp_metext);
pthread_mutex_unlock(&hp_metext);
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("pthread_mutex_t: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
NSLock
NSLock
是Foundation
框架提供的鎖:
/** NSlock 性能 */
{
NSLock *hp_lock = [NSLock new];
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
[hp_lock lock];
[hp_lock unlock];
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("NSlock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
NSCondition
/** NSCondition 性能 */
{
NSCondition *hp_condition = [NSCondition new];
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
[hp_condition lock];
[hp_condition unlock];
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("NSCondition: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
pthread_mutex_t(recursive)
/** PTHREAD_MUTEX_RECURSIVE 性能 */
{
pthread_mutex_t hp_metext_recurive;
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&hp_metext_recurive, &attr);
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
pthread_mutex_lock(&hp_metext_recurive);
pthread_mutex_unlock(&hp_metext_recurive);
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("PTHREAD_MUTEX_RECURSIVE: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
NSRecursiveLock
/** NSRecursiveLock 性能 */
{
NSRecursiveLock *hp_recursiveLock = [NSRecursiveLock new];
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
[hp_recursiveLock lock];
[hp_recursiveLock unlock];
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("NSRecursiveLock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
NSConditionLock
/** NSConditionLock 性能 */
{
NSConditionLock *hp_conditionLock = [NSConditionLock new];
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
[hp_conditionLock lock];
[hp_conditionLock unlock];
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent() ;
printf("NSConditionLock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
@synchronized
/** @synchronized 性能 */
{
double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
for (int i = 0 ; i < hp_runTimes; i++) {
@synchronized(self) {}
}
double_t hp_endTime = CFAbsoluteTimeGetCurrent();
printf("@synchronized: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}
鎖內(nèi)部沒有處理任何邏輯,都執(zhí)行的空操作浊伙,在10
萬次循環(huán)后計算時間差值撞秋。
1.2 驗(yàn)證
iPhone 12 pro max 14.3
真機(jī)測試數(shù)據(jù)如下:
OSSpinLock: 1.366019 ms
dispatch_semaphore_t: 1.923084 ms
os_unfair_lock_lock: 1.502037 ms
pthread_mutex_t: 1.694918 ms
NSlock: 2.384901 ms
NSCondition: 2.082944 ms
PTHREAD_MUTEX_RECURSIVE: 3.449082 ms
NSRecursiveLock: 3.075957 ms
NSConditionLock: 7.895947 ms
@synchronized: 3.794074 ms
iPhone 12 pro max 14.3
模擬器測試數(shù)據(jù)如下:
OSSpinLock: 1.199007 ms
dispatch_semaphore_t: 1.991987 ms
os_unfair_lock_lock: 1.762986 ms
pthread_mutex_t: 2.611995 ms
NSlock: 2.719045 ms
NSCondition: 2.544045 ms
PTHREAD_MUTEX_RECURSIVE: 4.145026 ms
NSRecursiveLock: 5.039096 ms
NSConditionLock: 8.215070 ms
@synchronized: 10.205030 ms
對比如下:
大部分鎖在真機(jī)上性能表現(xiàn)更好,@synchronized
在真機(jī)與模擬器中表現(xiàn)差異巨大嚣鄙。也就是說蘋果在真機(jī)模式下優(yōu)化了@synchronized
的性能吻贿。與之前相比目前@synchronized
的性能基本能滿足要求。
判斷一把鎖的性能好壞哑子,一般情況下是與pthread_mutex_t
做對比(因?yàn)榈讓佣际菍λ姆庋b)舅列。
二、@synchronized
由于@synchronized
使用比較簡單卧蜓,并且目前真機(jī)性能也不錯帐要。所以先分析它。
2.1售票案例
有如下代碼:
@property (nonatomic, assign) NSUInteger ticketCount;
- (void)testTicket {
self.ticketCount = 10;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 2; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"當(dāng)前余票還剩:%lu張",(unsigned long)self.ticketCount);
} else {
NSLog(@"當(dāng)前車票已售罄");
}
}
模擬了多線程售票請款弥奸,輸出如下:
當(dāng)前余票還剩:6張
當(dāng)前余票還剩:7張
當(dāng)前余票還剩:7張
當(dāng)前余票還剩:7張
當(dāng)前余票還剩:4張
當(dāng)前余票還剩:4張
當(dāng)前余票還剩:3張
當(dāng)前余票還剩:2張
當(dāng)前余票還剩:1張
當(dāng)前余票還剩:0張
當(dāng)前車票已售罄
當(dāng)前車票已售罄
當(dāng)前車票已售罄
當(dāng)前車票已售罄
當(dāng)前車票已售罄
可以看到余票數(shù)量有重復(fù)以及順序混亂榨惠。
saleTicket
加上@synchronized
就能解決問題:
- (void)saleTicket {
@synchronized(self) {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"當(dāng)前余票還剩:%lu張",(unsigned long)self.ticketCount);
} else {
NSLog(@"當(dāng)前車票已售罄");
}
}
}
一般參數(shù)傳遞self
。那么有以下疑問:
- 為什么要傳
self
呢盛霎?傳nil
行不行赠橙? -
@synchronized
是怎么實(shí)現(xiàn)加鎖的效果的呢? -
{}
代碼塊究竟是什么呢愤炸? - 是否可以遞歸呢期揪?
- 底層是什么數(shù)據(jù)結(jié)構(gòu)呢?
2.2 clang 分析 @synchronized
@synchronized
是個系統(tǒng)關(guān)鍵字规个,那么通過clang
還原它的底層實(shí)現(xiàn)凤薛,為了方便實(shí)現(xiàn)在main
函數(shù)中調(diào)用它:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized(appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
clang
還原后代碼如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
{ __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
{
id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {
objc_sync_exit(sync_exit);
}
id sync_exit;
} _sync_exit(_sync_obj);
}
catch (id e) {
_rethrow = e;
}
{
struct _FIN {
_FIN(id reth) : rethrow(reth) {}
~_FIN() {
if (rethrow) objc_exception_throw(rethrow);
}
id rethrow;
} _fin_force_rethow(_rethrow);
}
}
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
異常處理不關(guān)心,所以核心就是try
的邏輯绰姻,精簡后如下:
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {
objc_sync_exit(sync_exit);
}
id sync_exit;
} _sync_exit(_sync_obj);
_SYNC_EXIT
是個結(jié)構(gòu)體的定義枉侧,_sync_exit
析構(gòu)的實(shí)現(xiàn)是objc_sync_exit(sync_exit)
,所以@synchronized
本質(zhì)上等價于enter + exit
:
//@synchronized(appDelegateClassName) {}
//等價
objc_sync_enter(appDelegateClassName);
objc_sync_exit(appDelegateClassName);
它們是定義在objc
中的狂芋。當(dāng)然也可以通過對@synchronized
打斷點(diǎn)查看匯編定位:
2.3 源碼分析
2.3.1 objc_sync_enter
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
//obj存在的情況下 獲取 SyncData
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");
}
//不存在調(diào)用objc_sync_nil
objc_sync_nil();
}
return result;
}
-
obj
存在的情況下通過id2data
獲取SyncData
榨馁,參數(shù)是obj
與ACQUIRE
。- 然后通過
mutex.lock()
加鎖帜矾。
- 然后通過
-
obj
為nil
的情況下調(diào)用objc_sync_nil
翼虫,根據(jù)注釋does nothing
是一個空實(shí)現(xiàn)。
mutex
mutex
是recursive_mutex_t mutex
類型屡萤,本質(zhì)上是recursive_mutex_tt
:
using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;
class recursive_mutex_tt : nocopy_t {
os_unfair_recursive_lock mLock;
......
}
typedef struct os_unfair_recursive_lock_s {
os_unfair_lock ourl_lock;
uint32_t ourl_count;
} os_unfair_recursive_lock, *os_unfair_recursive_lock_t;
os_unfair_recursive_lock
是對os_unfair_lock
的封裝珍剑。所以 @synchronized 是對os_unfair_lock 的封裝。
objc_sync_nil
objc_sync_nil
的定義如下:
BREAKPOINT_FUNCTION(
void objc_sync_nil(void)
);
# define BREAKPOINT_FUNCTION(prototype) \
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
prototype { asm(""); }
替換還原后如下:
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
void objc_sync_nil(void) {
asm("");
}
也就是一個空實(shí)現(xiàn)死陆。
2.3.2 objc_sync_exit
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;//0
if (obj) {
//獲取 SyncData
SyncData* data = id2data(obj, RELEASE);
if (!data) {//沒有輸出返回錯誤code - 1
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
//獲取到數(shù)據(jù)先解鎖
bool okay = data->mutex.tryUnlock();
if (!okay) {//解鎖失敗返回-1
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
-
obj
存在的情況下通過id2data
獲取SyncData
招拙,參數(shù)是obj
與RELEASE
唧瘾。 - 獲取到數(shù)據(jù)進(jìn)行解鎖,解鎖成功返回
0
别凤,失敗返回-1
饰序。
2.3.3 SyncData 數(shù)據(jù)結(jié)構(gòu)
SyncData
是一個結(jié)構(gòu)體:
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;//下一個節(jié)點(diǎn)
DisguisedPtr<objc_object> object;//obj,@synchronized的參數(shù)
int32_t threadCount; // number of THREADS using this block 線程數(shù)量
recursive_mutex_t mutex;//鎖
} SyncData;
-
nextData
指向下一個節(jié)點(diǎn),SyncData
是一個單向鏈表规哪。 -
object
存儲的是@synchronized
的參數(shù)求豫,只不過進(jìn)行了包裝。 -
threadCount
代表線程數(shù)量诉稍。支持多線程訪問蝠嘉。 -
mutex
創(chuàng)建的鎖。遞歸鎖只能遞歸使用不能多線程使用杯巨。
三蚤告、id2data
objc_sync_enter
與objc_sync_exit
中都調(diào)用了id2data
獲取數(shù)據(jù),區(qū)別是第二個參數(shù)舔箭,顯然id2data
就是數(shù)據(jù)處理的核心了罩缴。
進(jìn)行代碼塊折疊后有如下邏輯:
syndata
要么從TLS
獲取,要么從cache
獲取层扶。都沒有的情況下進(jìn)行創(chuàng)建箫章。
3.1 SyncData存儲結(jié)構(gòu)
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
//本身也是 os_unfair_lock
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
可以看到鎖和SyncData
都是從sDataLists
獲取的(hash map
結(jié)構(gòu),存儲的是SyncList
)镜会,SyncList
定義如下:
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
StripedMap
定義如下:
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];
......
}
在iOS
真機(jī)上容量為8
檬寂,其它平臺容量為64
。SynData
根據(jù)前面的分析是一個單向鏈表戳表, 那么可以得到在哈希沖突的時候是采用拉鏈法解決的桶至。
增加以下驗(yàn)證代碼:
HPObject *obj = [HPObject alloc];
HPObject *obj2 = [HPObject alloc];
HPObject *obj3 = [HPObject alloc];
dispatch_async(dispatch_queue_create("HotpotCat", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (obj) {
NSLog(@"obj");
@synchronized (obj2) {
NSLog(@"obj2");
@synchronized (obj3) {
NSLog(@"obj3");
}
}
}
});
斷點(diǎn)驗(yàn)證:
-
sDataLists
包裝了array
,其中存儲的是SyncList
集合匾旭,SyncList
的data
中存儲的是synData
镣屹。
3.2 從 TLS 獲取 SyncData
bool fastCacheOccupied = NO;//后續(xù)存儲的時候用
//對 pthread_getspecific 的封裝,針對線程中第一次調(diào)用 @synchronized 是獲取不到數(shù)據(jù)的价涝。
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
//判斷要查找的與存儲的object是不是同一個女蜈。
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//獲取當(dāng)前線程對該對象鎖了幾次
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: {//enter 的時候 lockCount + 1,并且存儲count到tls
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE: //exit的時候 lockCount - 1色瘩,并且存儲count到tls
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
//當(dāng) count 減少到 0 的情況下清除對應(yīng)obj的SynData伪窖,這里并沒有清空count,count在存儲新objc的時候直接賦值為1
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
//threadCount - 1
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
- 通過
tls_get_direct
(是對_os_tsd_get_direct
的封裝)獲取當(dāng)前線程存儲的SynData
數(shù)據(jù)居兆。 - 在數(shù)據(jù)存在的情況下判斷標(biāo)記
fastCacheOccupied
存在覆山。 - 判斷
tls
存儲的數(shù)據(jù)是不是當(dāng)前對象。是當(dāng)前對象則進(jìn)行進(jìn)一步處理泥栖,否則結(jié)束tls
邏輯簇宽。 - 獲取對象加鎖的次數(shù)
lockCount
勋篓。 -
enter
邏輯:lockCount++
并存儲在tls
。 -
exit
邏輯:lockCount--
并存儲在tls
晦毙。- 當(dāng)
lockCount
為0
的時候釋放SynData
生巡,直接在tls
中置為NULL
。 - 并且
threadCount - 1
见妒。
- 當(dāng)
線程局部存儲(
Thread Local Storage
,TLS
): 是操作系統(tǒng)為線程單獨(dú)提供的私有空間,通常只有有限的容量甸陌。
Linux
系統(tǒng)下通常通過pthread
庫中的相關(guān)方法進(jìn)行操作:
pthread_key_create()
须揣、
pthread_getspecific()
、
pthread_setspecific()
钱豁、
pthread_key_delete()
3.3 從 Cache 獲取 SyncData
當(dāng)tls
中沒有找到SynData
的時候會去Cache
中找:
//獲取線程緩存耻卡,參數(shù)NO 當(dāng)緩存不存在的時候不進(jìn)行創(chuàng)建。
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
//找到obj對應(yīng)的 item
if (item->data->object != object) continue;
// Found a match.
//獲取SynData
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE://enter lockCount + 1
item->lockCount++;
break;
case RELEASE://exit lockCount - 1
item->lockCount--;
if (item->lockCount == 0) {//lockCount = 0 的時候 從cache中移除i的元素牲尺,將最后一個元素存儲到原先i的位置卵酪。used - 1。也就是最后一個位置被標(biāo)記為未使用了谤碳。
// remove from per-thread cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
//threadCount - 1
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
- 通過
fetch_cache
(是對pthread_getspecific
的封裝)找SyncCache
溃卡,由于是讀取數(shù)據(jù),所以找不到的情況下這里不創(chuàng)建蜒简。 - 遍歷
cache
已使用的空間找到obj
對應(yīng)的SyncCacheItem
瘸羡。 -
enter
的情況下item->lockCount++
。 -
exit
情況下item->lockCount--
- 當(dāng)
item->lockCount == 0
的時候?qū)?code>cache中這個item
替換為cache
中最后一個搓茬,used -1
標(biāo)記cache
中使用的數(shù)量犹赖,這樣就將cache
中數(shù)據(jù)釋放了。 -
syndata
的threadCount
進(jìn)行-1
卷仑。
- 當(dāng)
3.3.1 SyncCache
typedef struct {
SyncData *data;//數(shù)據(jù)
unsigned int lockCount; // 被當(dāng)前線程加鎖次數(shù)
} SyncCacheItem;
typedef struct SyncCache {
unsigned int allocated;//總?cè)萘? unsigned int used;//已使用
SyncCacheItem list[0];//列表
} SyncCache;
-
SyncCache
中存儲的是SyncCacheItem
的一個list
峻村,allocated
用于記錄開辟的總?cè)萘浚?code>used記錄已經(jīng)使用的容量。 -
SyncCacheItem
存儲了一個SyncData
以及lockCount
锡凝。記錄的是針對當(dāng)前線程SyncData
被鎖了多少次粘昨。SyncCacheItem
存儲的對應(yīng)于TSL
快速緩存的SYNC_COUNT_DIRECT_KEY
與SYNC_DATA_DIRECT_KEY
。
3.3.2 fetch_cache
static SyncCache *fetch_cache(bool create)
{
_objc_pthread_data *data;
//creat用來處理是否新建私爷。
data = _objc_fetch_pthread_data(create);
//data不存在直接返回雾棺,create為YES的情況下data不會為空
if (!data) return NULL;
//syncCache不存在
if (!data->syncCache) {
if (!create) {//不允許創(chuàng)建直接返回 NULL
return NULL;
} else {
//允許創(chuàng)建直接 calloc 創(chuàng)建,初始容量為4.
int count = 4;
data->syncCache = (SyncCache *)
calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
data->syncCache->allocated = count;
}
}
// Make sure there's at least one open slot in the list.
//存滿的情況下擴(kuò)容 2倍擴(kuò)容衬浑。
if (data->syncCache->allocated == data->syncCache->used) {
data->syncCache->allocated *= 2;
data->syncCache = (SyncCache *)
realloc(data->syncCache, sizeof(SyncCache)
+ data->syncCache->allocated * sizeof(SyncCacheItem));
}
return data->syncCache;
}
- 通過
_objc_fetch_pthread_data
獲取_objc_pthread_data
捌浩,_objc_pthread_data
存儲了SyncCache
信息,當(dāng)然不僅僅是它:
image.png -
data
不存在直接返回工秩,create
為YES
的情況下data
不會為空尸饺。 -
syncCache
不存在的情況下进统,允許創(chuàng)建則進(jìn)行calloc
(初始容量4
,這里是創(chuàng)建syncCache
)浪听,否則返回NULL
螟碎。 -
syncCache
存滿(通過allocated
與used
判斷)的情況下進(jìn)行2
被擴(kuò)容。
_objc_fetch_pthread_data
_objc_pthread_data *_objc_fetch_pthread_data(bool create)
{
_objc_pthread_data *data;
//pthread_getspecific TLS_DIRECT_KEY
data = (_objc_pthread_data *)tls_get(_objc_pthread_key);
if (!data && create) {
//允許創(chuàng)建的的情況下創(chuàng)建
data = (_objc_pthread_data *)
calloc(1, sizeof(_objc_pthread_data));
//保存
tls_set(_objc_pthread_key, data);
}
return data;
}
- 通過
tls_get
獲取_objc_pthread_data
迹栓,不存在并且允許創(chuàng)建的情況下進(jìn)行calloc
創(chuàng)建_objc_pthread_data
掉分。 - 創(chuàng)建后保存到
tls
。
這里的cache
也是存儲在tls
克伊,與tls_get_direct
的區(qū)別要看二者存取的邏輯酥郭,一個調(diào)用的是tls_get_direct
,一個是tls_get
:
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif
#if SUPPORT_DIRECT_THREAD_KEYS
#define _objc_pthread_key TLS_DIRECT_KEY
#else
static tls_key_t _objc_pthread_key;
#endif
//key _objc_pthread_key
static inline void *tls_get(tls_key_t k) {
return pthread_getspecific(k);
}
//key SYNC_DATA_DIRECT_KEY 與 SYNC_COUNT_DIRECT_KEY
static inline void *tls_get_direct(tls_key_t k)
{
ASSERT(is_valid_direct_key(k));
if (_pthread_has_direct_tsd()) {
return _pthread_getspecific_direct(k);
} else {
return pthread_getspecific(k);
}
}
__header_always_inline int
_pthread_has_direct_tsd(void)
{
#if TARGET_IPHONE_SIMULATOR
return 0;
#else
return 1;
#endif
}
__header_always_inline void *
_pthread_getspecific_direct(unsigned long slot)
{
#if TARGET_IPHONE_SIMULATOR
return pthread_getspecific(slot);
#else
return _os_tsd_get_direct(slot);
#endif
}
__attribute__((always_inline))
static __inline__ void*
_os_tsd_get_direct(unsigned long slot)
{
return _os_tsd_get_base()[slot];
}
-
_objc_pthread_data
通過pthread_getspecific
獲取緩存數(shù)據(jù)愿吹,key
的類型是tls_key_t
:- 如果支持
SUPPORT_DIRECT_THREAD_KEYS
不从,key
為__PTK_FRAMEWORK_OBJC_KEY0
。 - 不支持
SUPPORT_DIRECT_THREAD_KEYS
犁跪,key
為_objc_pthread_key
椿息。
- 如果支持
-
TLS
快速緩存通過tls_get_direct
獲取,key
是tls_key_t
類型坷衍。-
SynData
對應(yīng)的key
為__PTK_FRAMEWORK_OBJC_KEY1
寝优。 -
lockCount
對應(yīng)的key
是__PTK_FRAMEWORK_OBJC_KEY2
。 -
iOS
模擬器通過pthread_getspecific
獲取 - 其它通過
_os_tsd_get_direct
獲取惫叛,調(diào)用的是_os_tsd_get_base()
倡勇,不同架構(gòu)對應(yīng)不同匯編指令:
-
__attribute__((always_inline, pure))
static __inline__ void**
_os_tsd_get_base(void)
{
#if defined(__arm__)
uintptr_t tsd;
__asm__("mrc p15, 0, %0, c13, c0, 3\n"
"bic %0, %0, #0x3\n" : "=r" (tsd));
/* lower 2-bits contain CPU number */
#elif defined(__arm64__)
uint64_t tsd;
__asm__("mrs %0, TPIDRRO_EL0\n"
"bic %0, %0, #0x7\n" : "=r" (tsd));
/* lower 3-bits contain CPU number */
#endif
return (void**)(uintptr_t)tsd;
}
3.4 從sDataLists獲取SynData
//sDataLists 中找 Syndata
{
SyncData* p;
SyncData* firstUnused = NULL;
//從SynList鏈表中查找SynData
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;//找到
// atomic because may collide with concurrent RELEASE
//threadCount + 1,由于在上面線程緩存和tls的查找中沒有找到嘉涌,但是在 sDataLists 中找到了妻熊。所以肯定不是同一個線程了(那也肯定就不是exit,而是enter了)仑最,線程數(shù)量+1扔役。
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
//沒有找到的情況下找到了空位。
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
//是exit就直接跳轉(zhuǎn)到done的邏輯
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
//找到一個未使用的(也有可能是之前使用過警医,threadCount現(xiàn)在變?yōu)?了)亿胸,直接存儲當(dāng)前objc數(shù)據(jù)(這里相當(dāng)于釋放了sDataLists中的舊數(shù)據(jù))。
if ( firstUnused != NULL ) {
result = firstUnused;
//替換object
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
- 遍歷開始獲取的
SynList
找obj
對應(yīng)的SynData
预皇。 - 找到的情況下
threadCount + 1
侈玄,由于在tls
(快速以及cache
中)沒有找到數(shù)據(jù),但是在sDataLists
中找到了吟温,所以肯定不在同一個線程(那也肯定就不是exit
序仙,而是enter
了)直接跳轉(zhuǎn)到done
。 -
eixt
的邏輯直接跳轉(zhuǎn)到done
鲁豪。 - 沒有找到但是找到了
threadCount = 0
的Syndata
潘悼,也就是找到了空位(之前使用過律秃,threadCount
現(xiàn)在變?yōu)?code>0了)。- 直接存儲當(dāng)前
objc
數(shù)據(jù)到synData
中(這里相當(dāng)于釋放了sDataLists
中的舊數(shù)據(jù))治唤。threadCount
標(biāo)記為1
棒动。
- 直接存儲當(dāng)前
3.5 創(chuàng)建 SyncData
當(dāng)tls
中沒有快速緩存、也沒cache
宾添、并且sDataLists
中沒有數(shù)據(jù)也沒有空位:
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
//對象本身
result->object = (objc_object *)object;
//持有線程數(shù)初始化為1
result->threadCount = 1;
//創(chuàng)建鎖
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
//頭插法
result->nextData = *listp;
//這里 sDataLists 中的 SynList就賦值了船惨。
*listp = result;
- 開辟一個
SyncData
大小的內(nèi)存并進(jìn)行對齊。 - 設(shè)置
object
以及threadCount
缕陕。 - 創(chuàng)建
mutex
鎖掷漱。 - 頭插法將創(chuàng)建的
SynData
插入SynList
中。也就相當(dāng)于將數(shù)據(jù)存入sDataLists
中榄檬。nextData
存在的情況是發(fā)生了哈希沖突。
3.6 done 緩存存儲邏輯
//數(shù)據(jù)存儲
if (result) {//有result衔统,無論是創(chuàng)建的還是從 sDataLists 獲取的鹿榜。
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {//exit不進(jìn)行任何操作
// 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
//TLS 快速緩存不存在,存儲到快速緩存锦爵。
if (!fastCacheOccupied) {//
// Save in fast thread cache
//存儲Syndata
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
//存儲count為1
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
//cache存儲 不支持 tls 快速緩存 或者 tls快速緩存存在的情況下
{
// Save in thread cache
//獲取SyncCache舱殿,不存在的時候進(jìn)行創(chuàng)建
if (!cache) cache = fetch_cache(YES);
//將result放入list的最后一個元素,SyncCacheItem 中存儲 result 以及 lockCount
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
-
exit
的時候不進(jìn)行任何操作:-
TLS
快速緩存會在獲取緩存的時候進(jìn)行釋放险掀。并且threadCount -1
沪袭。 -
cache
邏輯會進(jìn)行替換數(shù)據(jù)(相當(dāng)于釋放),并且threadCount -1
樟氢。 -
sDataLists
獲取數(shù)據(jù)邏輯本身不釋放冈绊,會根據(jù)threadCount = 0
找到空位進(jìn)行替換,相當(dāng)于釋放埠啃。
-
- 在支持快速緩存并且快速緩存不存在的情況下死宣,將創(chuàng)建的
SynData
以及lockCount = 1
存儲到TLS
快速緩存中。 - 在不支持快速緩存或者快速緩存已經(jīng)有值了的情況下將
SynData
構(gòu)造SyncCacheItem
存入SyncCache
中碴开。 - 也就是說
SynData
只會在快速緩存與Cache
中存在一個毅该,同時會存儲在sDataLists
中。
3.7 驗(yàn)證
3.7.1 @synchronized 數(shù)據(jù)結(jié)構(gòu)
根據(jù)源碼分析@synchronized
數(shù)據(jù)結(jié)構(gòu)如下:
3.7.2 驗(yàn)證
有如下代碼:
HPObject *obj = [HPObject alloc];
HPObject *obj2 = [HPObject alloc];
HPObject *obj3 = [HPObject alloc];
dispatch_async(dispatch_queue_create("HotpotCat", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (obj) {
@synchronized (obj) {
@synchronized (obj) {
//obj lockCount = 3 threadCount = 1
NSLog(@"1 = %p",obj);
@synchronized (obj2) {
//obj2 lockCount = 1 threadCount = 1潦牛,有可能存在拉鏈
NSLog(@"2 = %p",obj2);
@synchronized (obj3) {
//obj3 threadCount = 1, lockCount = 1眶掌,必然存在拉鏈(為了方便驗(yàn)證源碼強(qiáng)制修改StripeCount為2)
NSLog(@"3 = %p",obj3);
dispatch_async(dispatch_queue_create("HotpotCat1", DISPATCH_QUEUE_CONCURRENT), ^{
@synchronized (obj) {
//obj threadCount = 2,一個線程的 lockCount = 3 另外一個 lockCount = 1
NSLog(@"4 = %p",obj);
}
});
//為了讓 @synchronized 不exit
sleep(10);
}
}
}
}
}
});
do {
} while (1);
由于源碼是mac
工程,在main
函數(shù)中寫一個死循環(huán)巴碗。為了方便驗(yàn)證將源碼中StripeCount
改為2
:
在
NSLog
的@synchronized
處斷點(diǎn)驗(yàn)證朴爬。
-
1
處的驗(yàn)證結(jié)果:
image.png
lockCount = 3
,threadCount = 1
良价,并且sDataLists
中存儲的與快速緩存中是同一個SynData
地址寝殴。符合預(yù)期蒿叠。 -
2
處驗(yàn)證結(jié)果:
image.png
可以看到這個時候第二個元素已經(jīng)進(jìn)行了拉鏈,并且obj2
在鏈表的頭結(jié)點(diǎn)蚣常。
-
3
處結(jié)果驗(yàn)證:
image.png
仍然進(jìn)行了拉鏈obj3 -> obj2 -> obj
市咽。
-
4
處驗(yàn)證結(jié)果:
image.png
這個時候obj
對應(yīng)的SynData
的threadCount
是2
了。
所有驗(yàn)證結(jié)果符合分析預(yù)期抵蚊。
四施绎、總結(jié)
參數(shù)傳
nil
沒有做任何事情。傳self
在使用過程中不會被釋放贞绳,并且同一個類中如果都用self
底層只會存在一個SynData
谷醉。@synchronized
底層是封裝的os_unfair_lock
。objc_sync_enter
中加鎖冈闭,objc_sync_exit
中解鎖俱尼。@synchronized
加鎖的數(shù)據(jù)信息都存儲在sDataLists
全局哈希表中。同時還有TLS
快速緩存(一個SynData
數(shù)據(jù)萎攒,通常是第一個遇八,釋放后會存放新的)以及線程緩存(一組SyncData
數(shù)據(jù))。這兩個緩存互斥耍休,同一個SyncData
只存在其中一個)-
id2data
獲取SynData
流程:-
TLS
快速緩存獲热杏馈(SYNC_COUNT_DIRECT_KEY
),obj
對應(yīng)的SyncData
存在的情況下獲取SYNC_COUNT_DIRECT_KEY
對應(yīng)的lockCount
羊精。-
enter
:lockCount++
并存儲到SYNC_COUNT_DIRECT_KEY
斯够。 -
exit
:lockCount--
并存儲到SYNC_COUNT_DIRECT_KEY
。lockCount == 0
清空SYNC_DATA_DIRECT_KEY
喧锦,threadCount -1
读规。
-
-
TLS cache
緩存獲取,遍歷cache
找到對應(yīng)的SyncData
裸违。-
enter
:lockCount++
掖桦。 -
exit
:lockCount--
。lockCount == 0
替換cache->list
對應(yīng)的值為最后一個供汛,used -1
枪汪,threadCount -1
。
-
-
sDataLists
全局哈希表獲取SyncData
:找到的情況下threadCount + 1
進(jìn)入緩存邏輯怔昨,沒有找到并且存在threadCount = 0
則替換object
相當(dāng)于存儲了新值雀久。 -
SyncData
創(chuàng)建:創(chuàng)建SyncData
,賦值object
趁舀,threadCount
初始化為1
赖捌,創(chuàng)建mutex
鎖。并且采用頭插法將SyncData
插入sDataLists
對應(yīng)的SynList
頭部。 -
SyncData
數(shù)據(jù)緩存:sDataLists
添加了或者更新了數(shù)據(jù)會走到緩存邏輯越庇,緩存邏輯是往TLS
快速緩存以及TLS cache
緩存添加數(shù)據(jù)-
enter
:TLS
快速緩存不存在的情況下將SyncData
存儲快速緩存罩锐,否則存入cache
緩存的尾部。 -
exit
:直接return
卤唉。
-
-
-
lockCount
是針對單個線程而言的涩惑,當(dāng)lockCount = 0
的時候?qū)?shù)據(jù)進(jìn)行釋放-
TLS
快速緩存是直接設(shè)置為NULL
(只有一個SyncData
)。 -
TLS cache
緩存是直接用最后一個數(shù)據(jù)進(jìn)行替換(一組SyncData
)桑驱,然后used -1進(jìn)行釋放
竭恬。 - 同時
threadCount - 1
相當(dāng)于當(dāng)前線程釋放。
-
threadCount
是針對跨線程的熬的,在threadCount = 0
的時候并不立即釋放痊硕,而是在下次插入數(shù)據(jù)的時候進(jìn)行替換。sDataLists
保存所有的數(shù)據(jù)押框。lockCount
是@synchronized
可重入可遞歸的原因岔绸,threadCount
是@synchronized
可跨線程的原因。
@synchronized
數(shù)據(jù)之間關(guān)系:
@synchronized
完整調(diào)用流程: