OC底層探索24-synchronize鎖的原理

1、八大鎖效率

  • 八大鎖分別:
    • 自璇所:OSSpinLock。在iOS10以后該鎖被重寫(xiě),會(huì)在堵塞時(shí)進(jìn)行休眠;
    • 互斥鎖:NSLock、NScondition、NSRecursiceLock、NSConditionLock、@synchronize;以及更加偏底層:pthread_mutex、pthread_mutex(recursive);

2、synchronize探索入口

所有底層的探索都需要一個(gè)切入點(diǎn),像這樣的代碼段除了堆棧的方式,還有clang、查看匯編的方式。

@synchronized (self) {
    i += 1;
}

2.1 查看堆棧


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

2.2 匯編方式

  • 可以看到使用了@synchronize之后在方法塊前后調(diào)用了兩個(gè)方法objc_sync_enterobjc_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;
  1. 同一對(duì)象不同線程加鎖時(shí)會(huì)進(jìn)行原子的threadCount++;
  2. 由于list中SyncData不會(huì)進(jìn)行刪除,所以需要復(fù)用;
  3. 如果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ū)和我溝通!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 鎖的性能排行 鎖的歸類(lèi) 自旋鎖:線程反復(fù)檢查鎖變量是否可用。由于線程在這一過(guò)程中保持執(zhí)行,因此是一種忙等待。一旦獲...
    猿人閱讀 529評(píng)論 1 3
  • 《iOS底層原理文章匯總》[http://www.itdecent.cn/p/15af435341ce]上一篇文...
    一畝三分甜閱讀 911評(píng)論 0 0
  • 鎖的種類(lèi) 借用網(wǎng)上的一張有關(guān)鎖性能的對(duì)比圖,如下所示: 從上圖中我們可以看出來(lái),鎖大概可以分為以下幾種: 1.:在...
    含笑州閱讀 1,088評(píng)論 0 0
  • @synchronized 本質(zhì)是個(gè)遞歸鎖,不需要程序員手動(dòng)加解鎖,并且不會(huì)產(chǎn)生死鎖問(wèn)題,因此在開(kāi)發(fā)中的使用頻率比...
    正_文閱讀 3,511評(píng)論 0 14
  • 知 識(shí) 點(diǎn) / 超 人 @synchronized是一種對(duì)對(duì)象加鎖方式,跟互斥鎖類(lèi)似。當(dāng)你需要在多線程環(huán)境下控制某...
    樹(shù)下敲代碼的超人閱讀 795評(píng)論 0 9

友情鏈接更多精彩內(nèi)容