重學OC第二十一篇:@synchronized分析
一、clang分析@synchronized
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc] init];
@synchronized (obj) {
}
return 0;
}
通過clang -rewrite-objc main.m轉為main.cpp後@synchronized (obj) { }內容如下
{
id _rethrow = 0;
id _sync_obj = (id)obj;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {} //建構函式,通過arg初始化sync_exit
~_SYNC_EXIT() {objc_sync_exit(sync_exit);} //解構函式
id sync_exit;
} _sync_exit(_sync_obj); //把_sync_obj通過建構函式賦值給了sync_exit,它的生命週期是在try中,try執行完就會走析構呼叫objc_sync_exit
} 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);
}
}
可以看到主要是內容包括一個objc_sync_enter和一個try-catch-finally結構,重點關注try中的內容,通過_sync_exit(_sync_obj)初始化了一個sync_exit = _sync_obj的struct _SYNC_EXIT結構體物件,然後在try作用域執行完後呼叫它的解構函式自運執行objc_sync_exit。那麼可以看出@synchronized (obj) { }主要就是對obj進行objc_sync_enter和objc_sync_exit操作,以及丟擲異常。
二、objc_sync_enter()原始碼解析
打上符號斷點objc_sync_enter,找到objc_sync_enter屬於libobjc.A.dylib,檢視objc中objc_sync_enter原始碼。
//開始在obj上進行同步。如果有需要,會分配與obj關聯的遞迴互斥鎖。一旦獲取鎖成功返回OBJC_SYNC_SUCCESS。
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS; //OBJC_SYNC_SUCCESS=0
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");
}
//啥也不幹,asm("");
objc_sync_nil();
}
return result;
}
通過上面程式碼可以看到如果obj存在,通過id2data()函式拿到了data,呼叫data的mutex進行加鎖。如果obj為nil,objc_sync_enter什麼也不做。
2.1 id2data()函式分析
在看id2data()函式前,先來了解下其中用到的SyncData、SyncCacheItem、SyncCache、SyncList結構體。
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
//CacheLineSize = 64, alignas(CacheLineSize)表示按64位對齊
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex; //遞迴互斥鎖
} SyncData;
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;
static SyncData* id2data(id object, enum usage why)
{
//#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
//#define LIST_FOR_OBJ(obj) sDataLists[obj].data
//static StripedMap<SyncList> sDataLists;
//在StripedMap型別sDataLists表中通過object地址的雜湊值查詢SyncList對應的值
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
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;
//以SYNC_DATA_DIRECT_KEY為key從tls中取對應的data
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// 在tls中找到了
uintptr_t lockCount;
result = data;
//通過SYNC_COUNT_DIRECT_KEY為key從tls中取對應的上鎖次數
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進來,鎖數+1,存入tls
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE: //exit執行完了,鎖數-1,存入tls
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 //這條執行緒中沒有鎖了,把SyncData中的執行緒數量-1
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
//通過TLS_DIRECT_KEY查詢執行緒的SyncCache,初始可存4個SyncCacheItem,按2倍擴容
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: //enter,該item鎖數量+1
item->lockCount++;
break;
case RELEASE:
item->lockCount--; //exit,該item鎖數量-1
if (item->lockCount == 0) {
// remove from per-thread cache
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;
}
}
//到這執行緒快取未找到任何內容。
//防止多個執行緒為同一新物件建立多個鎖。
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
//SyncData單向連結串列查詢對應的object
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
//找到了執行緒數量+1
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;
// 找到第一個未使用的位置,把object和1設定給對應的data
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// 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.
//分配新的SyncData並新增到列表。
//呼叫posix_memalign( )成功時會返回sizeof(SyncData)位元組的動態記憶體,並且這塊記憶體的地址是alignof(SyncData)的倍數,記憶體塊的地址存放在&result裡
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
//這裡是一個c++叫做placement new特性的傢伙,就是不分配新記憶體,直接在&result->mutex這個地址上呼叫recursive_mutex_t的建構函式。fork_unsafe_lock是一個全域性鎖
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
//只有新的ACQUIRE才能到達這裡。所有的RELEASE和CHECK以及遞迴ACQUIRE都由上面的每個執行緒快取處理。
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
//就當作字典,SYNC_DATA_DIRECT_KEY為key, result為value
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// 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;
}
在上面在獲取快取時使用到了函式tls_get_direct和fetch_cache,這兩個函式都是與TLS(Thread Local Storage執行緒區域性儲存)相關的,它們只會獲取自己執行緒私有的資料,所以tls_get_direct和fetch_cache在不同的執行緒中查詢的是不一樣的。
通過id2data中for (p = *listp; p != NULL; p = p->nextData)
程式碼可以可知SyncList是單向連結串列
結構,當執行緒開多了,就會造成連結串列變長,連結串列太長且沒有快取時查詢就會很耗時;當使用屬性時要確保屬性不會為變nil,否則objc_sync_enter就不會起作用。
用threadCount來記錄有幾個執行緒在處理這個block,然後又用lockCount來記錄該block在同一個執行緒中上鎖的次數
,當lockCount為0時,說明這條執行緒中任務都完成了,threadCount數量-1。
三、objc_sync_exit原始碼
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
//這跟objc_sync_enter一樣,只是引數變為了RELEASE
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;
}
上面獲取data過程與objc_sync_enter使用同一函式id2data,主要看下tryUnlock的解鎖。
bool tryUnlock()
{
//os_unfair_recursive_lock_tryunlock4objc原始碼可以在libplatform中找到
if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) {
//清理鎖
lockdebug_recursive_mutex_unlock(this);
return true;
}
return false;
}
void lockdebug_recursive_mutex_unlock(recursive_mutex_t *lock)
{
auto& locks = ownedLocks();
if (!hasLock(locks, lock, RECURSIVE)) {
_objc_fatal("unlocking unowned recursive mutex");
}
clearLock(locks, lock, RECURSIVE);
}
總結
- @synchronized採用遞迴互斥鎖,可巢狀使用。
- 快取是採用TLS進行儲存的,用threadCount來記錄有幾個執行緒在處理@synchronized block,然後又用lockCount來記錄同一個執行緒中該block上鎖的次數。
- SyncList使用單向連結串列進行儲存,當連結串列太長又沒有快取可用時查詢就會很耗時。
- 注意@synchronized引數是否為nil,當為nil時是不會做事情的。
相關文章
- 重學OC第二十六篇:RunLoopOOP
- 重學OC第二十三篇:blockBloC
- 重學OC第二十四篇:啟動優化優化
- [第二十一篇]——Docker 安裝 RedisDockerRedis
- synchronized學習synchronized
- 深入學習synchronizedsynchronized
- 重學Swift第一篇:類結構探索Swift
- 深入分析 synchronized 關鍵字synchronized
- synchronized原理學習筆記synchronized筆記
- 增補部落格 第二十一篇 python 查詢鞍點Python
- OC常用數學函式及常量函式
- 深度學習在OC中的應用深度學習
- OC學習之旅------第九天
- Java 8 併發篇 - 冷靜分析 Synchronized(下)Javasynchronized
- 啃碎併發(七):深入分析Synchronized原理synchronized
- Java 8 併發篇 - 冷靜分析 Synchronized(上)Javasynchronized
- 資深程式設計師和你重學五線譜 - 第一篇程式設計師
- 簡單分析synchronized不會鎖洩漏的原因synchronized
- synchronized的jvm原始碼分析聊鎖的意義synchronizedJVM原始碼
- 分析oc物件的記憶體結構及其建立過程物件記憶體
- synchronizedsynchronized
- 盤一盤 synchronized (二)—— 偏向鎖批量重偏向與批量撤銷synchronized
- java執行緒同步:synchronized關鍵字,Lock介面以及可重Java執行緒synchronized
- listen原始碼分析第一篇 address:port分析原始碼
- 深入分析synchronized原理和鎖膨脹過程(二)synchronized
- synchronized 原理synchronized
- Synchronized bnsynchronized
- synchronized探究synchronized
- synchronized原理synchronized
- 【死磕Java併發】-----深入分析synchronized的實現原理Javasynchronized
- 探究synchronized底層原理(基於JAVA8原始碼分析)synchronizedJava原始碼
- 程式碼重構之法——方法重構分析
- OC(二)字串、方法字串
- 資料分析師如何寫一篇“有用”的分析報告
- java學習——併發專題——synchronized關鍵字Javasynchronized
- React Fiber原始碼分析 第一篇React原始碼
- 故障分析 | Redis AOF 重寫原始碼分析Redis原始碼
- Java學習筆記——第二十二天Java筆記