iOS 鎖的原理

  • 【互斥鎖】:用于多線程編程中,防止多條線程對統(tǒng)一資源讀寫,通過將代碼切割成一個(gè)個(gè)臨界區(qū)而達(dá)成

    • @synchronized
    • NSLock
    • pthread_mutex
  • 【自旋鎖】:線程會一直檢測鎖變量是否可用,因?yàn)榫€程在這過程一直保持執(zhí)行,所以線程會處于忙等狀態(tài),一旦獲取了自旋鎖,線程會一直持有該鎖,直至顯式釋放自旋鎖。

    • OSSpinLock
    • atomic
  • 【條件鎖】條件變量,當(dāng)進(jìn)程中的某些資源要求不滿足時(shí)就進(jìn)入休眠,加鎖,當(dāng)資源分配到了就解鎖,繼續(xù)運(yùn)行

    • NSCondition
    • pthread_mutex
  • 【遞歸鎖】同一線程可以加鎖很多次而不會死鎖,帶有遞歸性質(zhì)的互斥鎖

  • 【信號量】更高的的同步機(jī)制,互斥鎖可以說是semaphore在僅取值0/1時(shí)的特例,信號量可以有更多的取值空間來實(shí)現(xiàn)更加復(fù)雜的同步機(jī)制,而不是簡單的線程互斥

  • 【讀寫鎖】特殊的自旋鎖,并發(fā)性更高

鎖性能對比圖

OSSpinLock(自旋鎖) --> dispatch_semaphore(信號量) --> phread_mutex(互斥鎖) --> NSLock(互斥鎖) --> NSCondition(條件鎖) --> pthread_mutex(recursive)(互斥遞歸鎖) --> NSRecursiveLock(遞歸鎖) --> NSConditionLock(條件鎖) --> @synchronized(互斥鎖)

1、OSSpinLock(自旋鎖)

自從OSSpinLock出現(xiàn)安全問題,在iOS10之后就被廢棄了。自旋鎖之所以不安全,是因?yàn)楂@取鎖后,線程會一直處于忙等待,造成了任務(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中的屬性修飾符,自旋鎖,在mac開發(fā)中使用的多

setter方法

在底層中setter方法會根據(jù)不同的屬性修飾符調(diào)用不同方法,最后會統(tǒng)一調(diào)用reallySetProperty方法

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加鎖,spinlock在底層拋棄以前的OSSpinLock,使用os_unfair_lock替代實(shí)現(xiàn)加鎖,同時(shí)為了防止哈希沖突,實(shí)現(xiàn)了加鹽

using spinlock_t = mutex_tt<LOCKDEBUG>;

class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
    ...
}

getter方法

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、@synchronize(互斥遞歸鎖)

  • 通過匯編調(diào)試,可以發(fā)現(xiàn)@synchronize在執(zhí)行過程中,從objc_sync_enter開始到objc_sync_exit結(jié)束

    image.png

  • 通過clang查看底層編譯

    image.png

objc_sync_enter源碼

  • 如果obj存在,通過id2data方法獲取對應(yīng)的SyncData,對threadCount、lockCount進(jìn)行遞增
  • 如果obj不存在,調(diào)用objc_sync_nil,可以通過下符號斷點(diǎn)得知,改方法直接return了
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;
}

objc_sync_exit源碼

  • 如果obj存在,調(diào)用id2data獲取對應(yīng)的SyncData,對threadCount、lockCount進(jìn)行遞減
  • 如果obj不存在,直接return
// 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;
}

SyncData分析

SyncData是一個(gè)結(jié)構(gòu)體,表示一個(gè)線程data,類似鏈表結(jié)構(gòu),有next指向,封裝了recursive_mutex_t屬性,從而確定@ synchronized是一個(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;

SyncCache分析

SyncCache也是結(jié)構(gòu)體,用于存儲線程,其中list[0]表示當(dāng)前線程的鏈表data,主要存儲SyncDatalockCount

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

該方法加鎖和解鎖的復(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_get_direct方法中傳入線程Key,通過KVC的方式獲得綁定的SyncData,其中tls()表示本地局部的線程緩存
    • 判斷獲取的線程data是否存在,以及線程data中是否能找到對應(yīng)的object
    • 如果存在,在tls_get_direct方法中通過KVC方法獲取lockCount,記錄對象被鎖幾次(鎖的嵌套次數(shù))
    • 如果線程data中threadCount <= 0或者lockCount <= 0,直接奔潰
    • 通過傳入的why判斷操作類型
      • 如果是ACQUIRE,加鎖,lockCount++,保存到線程緩存中
      • 如果是RELEASE,解鎖,lockCount--,保存到線程緩存中,如果 lockCount==0,從線程緩存中移除
      • 如果是CHECK,什么也不做,直接return
  • 【第二步】在cache緩存中查找

    • 通過fetch_cache方法查找cache緩存中是否有線程
    • 如果有,遍歷cache總表,對出線程對應(yīng)的SyncCacheItem
    • SyncCacheItem中取出線程data
    • 判斷線程data的后續(xù)操作和【第一步】判斷一致
  • 【第三步】如果cache中也沒有,即第一次進(jìn)來,創(chuàng)建SyncData,并存儲到線程緩存中

    • 如果cache中找到線程,且與object相等,則進(jìn)行賦值、threadCount++
    • 如果cache沒有找到線程,threadCount==1

總結(jié)

  • @synchronized·在底層通過lockCount、threadCount,解決了遞歸互斥鎖和嵌套可重用,所以這個(gè)鎖是遞歸互斥鎖`

  • 使用鏈表結(jié)構(gòu),方便下一個(gè)data插入

  • 由于底層中有鏈表查詢、緩存查詢和遞歸,所有內(nèi)存和性能開銷大,所有如果嵌套次數(shù)過多,會導(dǎo)致底層查找麻煩,非常耗費(fèi)性能,但是使用簡單,不用手動(dòng)解鎖

  • 不能使用非oc對象加鎖,加鎖對象應(yīng)該一直存在內(nèi)存中,不能中途釋放,否則會奔潰

  • 底層流程

    • 【第一次進(jìn)入,沒有鎖】

      • threadCount = 1、lockCount = 1
      • 存儲到tls線程緩存
    • 【不是第一次進(jìn)入,且是同一個(gè)線程】

      • 當(dāng)tls線程緩存中有數(shù)據(jù),則lockCount++
      • 存儲到tls線程緩存

    -【不是第一次進(jìn)入,且是不同線程】
    - 根據(jù)線程Key全局線程空間查找對應(yīng)線程
    - threadCount ++、lockCount++
    - 存儲到cache

tls和cache緩存結(jié)構(gòu)

NSLock

NSLock底層是封裝了pthread_mutex,遵循了NSLocking協(xié)議,使用如下

NSLock *lock = [[NSLock alloc] init];
[lock lock];
[lock unlock];
  • NSLock是簡單的互斥鎖,不能嵌套遞歸使用,不然會出現(xiàn)一直等待的情況

NSRecursiveLock

NSRecursiveLock遞歸互斥鎖, 用于解決循環(huán)嵌套,在底層也是對pthread_mutex的封裝,底層實(shí)現(xiàn)和NSLock一致,區(qū)別在init時(shí)候,NSRecursiveLock的標(biāo)識是PTHREAD_MUTEX_RECURSIVE,而NSLock的標(biāo)識是默認(rèn)的

pthread_mutex

pthread_mutex互斥鎖,當(dāng)鎖被占用,其他線程申請鎖時(shí),不會一直忙等待,而是阻塞線程并睡眠,需要自己手動(dòng)釋放鎖和維護(hù)線程安全

// 導(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);

NSCondition

NSCondition條件鎖,和信號量類似,線程需要滿足條件才會繼續(xù)執(zhí)行,否則會堵塞等待,線程進(jìn)入休眠,直到條件滿足,常用于生成消費(fèi)者模型

  • NSCondition對象實(shí)際是一個(gè)和一個(gè)線程檢測器
    • :檢測條件時(shí)保護(hù)數(shù)據(jù)源,執(zhí)行條件時(shí)引發(fā)任務(wù)
    • 線程檢測器:根據(jù)條件決定是否繼續(xù)執(zhí)行線程,否則阻塞
  • 底層也是pthread_mutex的封裝
    • NSCondition是對mutexcond的一種封裝(cond是一種用于訪問和操作特定類型數(shù)據(jù)的指針)
//初始化
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];

NSConditionLock

NSConditionLock條件鎖,一旦一個(gè)線程獲得鎖,其他線程一定等待,其本質(zhì)是NSCondition + Lock,是對NSCondition的封裝,可以設(shè)置鎖條件,而NSCondition只是信號的通知

//初始化
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ù)比較條件

鎖性能總結(jié)

  • OSSpinLock自旋鎖由于安全性問題,在iOS10之后已經(jīng)被廢棄,在底層使用os_unfair_lock替代

    • OSSpinLock:線程會處于忙等狀態(tài)
    • os_unfair_lock:線程會處于休眠狀態(tài)
  • atomic原子鎖,自帶自旋鎖,保證setter、getter方法的線程安全

    • 屬性在調(diào)用setter、getter方法時(shí),會自動(dòng)加鎖osspinlock自旋鎖,避免屬性讀寫不同步
  • @synchronized在底層維護(hù)了一個(gè)哈希表用來存在線程data,通過鏈表表示可重用(嵌套)特性,性能低,但是簡單好用,多線程下適用性強(qiáng)

  • NSLock、NSRecursiveLock是互斥鎖,底層都是對pthread_mutex的封裝,區(qū)別在于init時(shí)的標(biāo)識符不一樣,NSLock不能用于嵌套遞歸,而NSRecursiveLock可以

  • NSCondition 、NSConditionLock是條件鎖,底層都是對pthread_mutex的封裝,當(dāng)滿足某一個(gè)條件時(shí)才能進(jìn)行操作,和信號量dispatch_semaphore類似

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

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

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