iOS底層探索25奏司、多線程 - 鎖

多線程的安全離不開鎖的使用瓷叫,常見鎖的性能:

image.png

一、鎖的分類

關(guān)于同步的Apple文檔

image.png

基本的鎖就包括了2大類:自旋鎖 互斥鎖.
其他的比如條件鎖楞陷、遞歸鎖怔鳖、信號量都是上層的封裝實(shí)現(xiàn).

  • 讀寫鎖 - (線程)讀單(線程)
    實(shí)際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者猜谚,讀者只對共享資源進(jìn)行讀訪問败砂,寫者則需要對共享資源進(jìn)行寫操作。讀寫鎖相對于自旋鎖而言魏铅,能提高并發(fā)性昌犹,因?yàn)樵?code>多處理器系統(tǒng)中,它允許同時(shí)有多個(gè)讀者來訪問共享資源览芳,最大可能的讀者數(shù)為實(shí)際的邏輯 CPU 數(shù)斜姥。寫者是排他性的,一個(gè)讀寫鎖同時(shí)只能有一個(gè)寫者或多個(gè)讀者(與CPU數(shù)相關(guān)),但不能同時(shí)既有讀者又有寫者铸敏。在讀寫鎖保持期間也是搶占失效的缚忧。
    1. 如果讀寫鎖當(dāng)前沒有讀者,也沒有寫者杈笔,那么寫者可以立刻獲得讀寫鎖闪水,否則寫者必須自旋在那里,直到?jīng)]有任何寫者或讀者蒙具;
    2. 如果讀寫鎖沒有寫者球榆,那么讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那里禁筏,直到寫者釋放該讀寫鎖持钉。

1、自旋鎖

  1. 線程反復(fù)檢查鎖變量是否可用篱昔。由于線程在這一過程中保持執(zhí)行每强, 因此是一種忙等待;
  2. 一旦獲取了自旋鎖州刽,線程會一直保持該鎖空执,直至顯式釋放自旋鎖;
  3. 自旋鎖避免了進(jìn)程上下文的調(diào)度開銷怀伦,因此對于線程只會阻塞很短時(shí)間的場合是有效的脆烟,小而精 的任務(wù);
  4. 因 一直檢查詢問鎖是否打開可用房待,耗費(fèi)性能比較高邢羔。

2、互斥鎖

  1. 是一種多線程編程中桑孩,防止兩條線程同時(shí)對同一公共資源(比如全局變量)進(jìn)行讀寫的機(jī)制拜鹤。它通過將代碼切片成一個(gè)一個(gè)的臨界區(qū) 而實(shí)現(xiàn)。
  2. 保證同一時(shí)間只有一條線程可進(jìn)行某執(zhí)行任務(wù) - 類似保證了同步的功能流椒。
    當(dāng)發(fā)現(xiàn)別的線程正在操作任務(wù)敏簿,當(dāng)前線程獲取互斥鎖失敗,當(dāng)前線程進(jìn)入休眠 (就緒狀態(tài) - 等待被調(diào)度執(zhí)行) --> 一直等到其他線程打開鎖之后 --> 喚起 執(zhí)行宣虾。
  3. 常見的互斥鎖 - 互斥鎖分為遞歸和非遞歸鎖
    3.1 NSLock
    3.2 @synchronized
    3.3 pthread_mutex

2.1) 遞歸鎖

  1. 就是同一個(gè)線程 可以加鎖 N 次而不會引發(fā)死鎖惯裕。
  2. 常見的遞歸鎖
    2.1 NSRecursiveLock
    2.2 pthread_mutex(recursive)

3、條件鎖

  1. 條件變量绣硝。當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠蜻势,也就
    是鎖住了。當(dāng)資源被分配到了鹉胖,條件鎖打開握玛,進(jìn)程繼續(xù)運(yùn)行够傍。
  2. 常見的條件鎖:
    2.1 NSCondition
    2.2 NSConditionLock

4、信號量 semaphore - dispatch_semaphore

  1. 信號量是一種更高級的同步機(jī)制挠铲,互斥鎖可以說是
    semaphore在僅取值0/1時(shí)的特例冕屯。
  2. 信號量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步拂苹,而不單單是線程間互斥安聘。

二、鎖的原理分析

以售票舉例瓢棒,示例代碼如下:

- (void)my_lockDemo {
    
    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 < 5; 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 < 10; i++) {
            [self saleTicket];
        }
    });
}

- (void)saleTicket {
        
    if (self.ticketCount > 0) {
        self.ticketCount--;
        sleep(0.1);
        NSLog(@"當(dāng)前余票還剩:%ld張",self.ticketCount);
        
    }else{
        NSLog(@"當(dāng)前車票已售罄");
    }
}

運(yùn)行程序搞挣,輸出結(jié)果如下:

/**
 當(dāng)前余票還剩:17張
 當(dāng)前余票還剩:18張
 當(dāng)前余票還剩:16張
 當(dāng)前余票還剩:18張
 當(dāng)前余票還剩:13張
 當(dāng)前余票還剩:13張
 當(dāng)前余票還剩:13張
 當(dāng)前余票還剩:12張
 ... 更多打印不必貼全 ...
 */

由上示例,余票票數(shù)是有問題的音羞,對于多線程操作,數(shù)據(jù)的安全性必須考慮仓犬。

1嗅绰、@synchronized 原理

給上面的示例代碼添加@synchronized鎖如下挣跋,再次運(yùn)行工程:

@synchronized (self) {
        
        if (self.ticketCount > 0) {
            self.ticketCount--;
            sleep(0.1);
            NSLog(@"當(dāng)前余票還剩:%ld張",self.ticketCount);
            
        }else{
            NSLog(@"當(dāng)前車票已售罄");
        }
}

/** 打印輸出結(jié)果如下:
 當(dāng)前余票還剩:19張
 當(dāng)前余票還剩:18張
 當(dāng)前余票還剩:17張
 當(dāng)前余票還剩:16張
 當(dāng)前余票還剩:15張
 當(dāng)前余票還剩:14張
 當(dāng)前余票還剩:13張
 當(dāng)前余票還剩:12張
 ... 更多打印不必貼全 ...
 */

由上基公,余票的數(shù)據(jù)是正確的吼畏,線程安全已解決悄窃。@synchronized是如何實(shí)現(xiàn)線程安全的呢檀轨?

  1. main.m文件編譯成cpp:

clang -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk /目標(biāo)文件路徑/main.m -o main3.cpp

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        @synchronized (appDelegateClassName) {
        }
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

/**************** 編譯后 .cpp --> @synchronized ******************/
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        
        { // 代碼塊區(qū)域
            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);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

由上文件可看到花鹅,@synchronized主要的2行代碼objc_sync_enterobjc_sync_exit着撩。

  1. 運(yùn)行工程夜矗,打開debug匯編調(diào)試也可找到objc_sync_enter / objc_sync_exit点骑,如下:
    0x104e8851e <+46>:  callq  0x104e8c754               ; symbol stub for: objc_sync_enter
    ... more info ... 
    0x104e885f1 <+257>: callq  0x104e8c75a               ; symbol stub for: objc_sync_exit

通過匯編酣难,跳進(jìn)到objc_sync_enter,找到其所在庫:libobjc.A.dylib

image.png

1.1黑滴、@synchronized源碼分析

打開 libobjc 源碼工程憨募,全局搜索:

// Begin synchronizing on 'obj'. 
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.  
// 遞歸互斥鎖 -- 嵌套
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    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");
        }
        objc_sync_nil();
    }

    return result;
}

1.1.1、@synchronized遞歸互斥鎖

SyncData: --> 鏈表結(jié)構(gòu)

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;

recursive_mutex_t --> recursive_mutex_tt:

template <bool Debug>
class recursive_mutex_tt : nocopy_t {
    os_unfair_recursive_lock mLock;

  public:
    constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) {
        lockdebug_remember_recursive_mutex(this);
    }

    constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
        : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT)
    { }

    void lock() // 鎖住
    {
        lockdebug_recursive_mutex_lock(this);
        os_unfair_recursive_lock_lock(&mLock);
    }

    void unlock() // 開鎖
    {
        lockdebug_recursive_mutex_unlock(this);

        os_unfair_recursive_lock_unlock(&mLock);
    }

    void forceReset()
    {
        lockdebug_recursive_mutex_unlock(this);

        bzero(&mLock, sizeof(mLock));
        mLock = os_unfair_recursive_lock OS_UNFAIR_RECURSIVE_LOCK_INIT;
    }

    bool tryLock()
    {
        if (os_unfair_recursive_lock_trylock(&mLock)) {
            lockdebug_recursive_mutex_lock(this);
            return true;
        }
        return false;
    }

    bool tryUnlock()
    {
        if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) {
            lockdebug_recursive_mutex_unlock(this);
            return true;
        }
        return false;
    }

    void assertLocked() {
        lockdebug_recursive_mutex_assert_locked(this);
    }

    void assertUnlocked() {
        lockdebug_recursive_mutex_assert_unlocked(this);
    }
};

由上源碼可驗(yàn)證@synchronized是個(gè)遞歸互斥鎖袁辈。

1.1.2菜谣、@synchronized的實(shí)現(xiàn)

id2data():

static SyncData* id2data(id object, enum usage why)
{
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    SyncData **listp = &LIST_FOR_OBJ(object);
    SyncData* result = NULL;

    // 1、快速查找
    // 支持線程 key晚缩,通過set get 存取
#if SUPPORT_DIRECT_THREAD_KEYS 
    // Check per-thread single-entry fast cache for matching object
    // 檢查每個(gè)線程 單條目快速緩存 是否匹配對象
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {// 當(dāng)前的 object 和data中的一致
            // Found a match in fast cache.
            uintptr_t lockCount;

            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++;// lockCount鎖的次數(shù) --> 可重復(fù)被鎖
                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
                    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

    // 2尾膊、緩存查找 - 遍歷哈希list
    // Check per-thread cache of already-owned locks for matching object
    // 檢查 已擁有鎖 的每個(gè)線程緩存 是否匹配對象
    SyncCache *cache = fetch_cache(NO);
    /**
    typedef struct SyncCache {
        unsigned int allocated;
        unsigned int used;
        SyncCacheItem list[0];
    } SyncCache;
    */
    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
                    // 從 緩存 list 中 remove
                    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.
    
    // 保證這塊的線程安全 lock
    lockp->lock();

    // 3、對象所對應(yīng)的鏈表 遍歷查找
    {
        SyncData* p;
        SyncData* firstUnused = NULL;
        for (p = *listp; p != NULL; p = p->nextData) {
            if ( p->object == object ) {
                result = p;
                // atomic because may collide with concurrent RELEASE
                OSAtomicIncrement32Barrier(&result->threadCount);
                goto done;
            }
            // 第一次進(jìn)來
            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
        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.
    // 沒有荞彼,創(chuàng)建新鏈表插入哈希list中
    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);// 遞歸自旋鎖
    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) {
            // Save in fast thread cache
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);// KVC
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);// lockCount
        } else 
#endif
        {
            // Save in thread cache 保存到哈希list
            if (!cache) cache = fetch_cache(YES);
            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }

    return result;
}

SyncData **listp = &LIST_FOR_OBJ(object);
-->LIST_FOR_OBJ():

#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
//
struct SyncList {
    SyncData *data;
    spinlock_t lock;

    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

// StripedMap:
template<typename T>
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;
    }
    ... more info ... 
}

SyncList結(jié)構(gòu):

image.png

  • @synchronized邏輯流程:
    對象objc@synchronized:
    1. 首先在棧存空間查找冈敛,匹配上:lockCount++并返回;
    2. 沒有繼續(xù)在全局的哈希list緩存查找卿泽,匹配到:lockCount++并返回莺债;
    3. 沒有找到,則開始遍歷當(dāng)前對象objc所對應(yīng)的鏈表遍歷查滋觉,找到,goto done齐邦,保存到緩存椎侠;
    4. 沒找到,創(chuàng)建結(jié)點(diǎn)給新鏈表措拇,
      4.1 若支持線程key - SUPPORT_DIRECT_THREAD_KEYS,通過tls_set_direct(k,value) - 以 KVC方式保存到tls(tls:本地局部的線程緩存)我纪;
      tls: 線程局部存儲(Thread Local Storage,TLS):是操作系統(tǒng)為線
      程單獨(dú)提供的私有空間,通常只有有限的容量
      丐吓。
      4.2 不支持浅悉,將新鏈表保存到緩存,即 開辟空間將其存到哈希list中券犁。

*問題:
@synchronized性能差原因由其實(shí)現(xiàn)原理也可知术健,鏈表的查找速度很慢,盡管做了緩存粘衬,但其速度仍是相較慢的荞估。
為何@synchronized性能那么低還要用它呢?
--> 使用方便稚新,封裝性高不用關(guān)心內(nèi)部加解鎖勘伺。
* 注意點(diǎn):
@synchronized()使用時(shí),需要鎖住的對象要注意其生命周期褂删,一般常見的是鎖self飞醉,其原因是對象的生命是和所在的self,并非都用self屯阀。
--> 我們在使用@synchronized()時(shí)考慮保證要鎖住的對象其生命正常即可缅帘。
示例代碼如下:

- (void)my_lock {
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

    for (int i=0; i<2000; i++) {
        
        // 1. 不加鎖 --> objc_release 野指針,對象多次釋放 --> crash
//        dispatch_async(dispatch_get_global_queue(0, 0), ^{
//            _muArray = [NSMutableArray array];
//        });
        
        // 2. @synchronized (self) --> 正常
//        dispatch_async(dispatch_get_global_queue(0, 0), ^{
//            @synchronized (self) {
//                _muArray = [NSMutableArray array];
//            }
//        });
        // 2.1 @synchronized (_muArray) --> crash
//        dispatch_async(dispatch_get_global_queue(0, 0), ^{
//            @synchronized (_muArray) {// 它可能在某時(shí)刻為nil,@synchronized鎖對nil不進(jìn)行任何操作so鎖不住
//                _muArray = [NSMutableArray array];
//            }
//        });
        
        // 3. 信號量 --> 正常
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        _muArray = [NSMutableArray array];
        dispatch_semaphore_signal(sem);
    }
}

2蹲盘、NSLock

2.1股毫、NSLock源碼

NSLockFoundation框架中,其為開源召衔,但swiftFoundation開源铃诬,我們這里以swiftFoundation源碼進(jìn)行探究。

open class NSLock: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
#if os(macOS) || os(iOS) || os(Windows)
    private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
    private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
#endif

    public override init() {
#if os(Windows)
        InitializeSRWLock(mutex)
        InitializeConditionVariable(timeoutCond)
        InitializeSRWLock(timeoutMutex)
#else
        pthread_mutex_init(mutex, nil)
#if os(macOS) || os(iOS)
        // lock 的創(chuàng)建必須 init苍凛,下面代碼可說明原因:
        // 條件數(shù)cond 和互斥mutex 的 init 操作
        pthread_cond_init(timeoutCond, nil)
        pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
    }
// ... ... more code ... ...
    open func lock() {
#if os(Windows)
        AcquireSRWLockExclusive(mutex)
#else
        pthread_mutex_lock(mutex)// 互斥鎖 lock
#endif
    }

    open func unlock() {
#if os(Windows)
        ReleaseSRWLockExclusive(mutex)
        AcquireSRWLockExclusive(timeoutMutex)
        WakeAllConditionVariable(timeoutCond)
        ReleaseSRWLockExclusive(timeoutMutex)
#else
        pthread_mutex_unlock(mutex)
#if os(macOS) || os(iOS)
        // Wakeup any threads waiting in lock(before:)
        pthread_mutex_lock(timeoutMutex) // 解鎖
        pthread_cond_broadcast(timeoutCond) // 廣播
        pthread_mutex_unlock(timeoutMutex) // 解鎖
#endif
#endif
    }

// ... ... more code ... ...
}

NSLock鎖的lockunlock是通過pthead進(jìn)行了一層封裝了趣席,NSLock鎖的性能次于pthread_mutex鎖一點(diǎn)點(diǎn)。

* Tip: NSLock - NSRecursiveLock - @synchronized三者使用場景

示例代碼:

- (void)my_NSLock {
    
    
    NSLock *lock = [[NSLock alloc]init];// 互斥鎖
    
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];// 遞歸鎖
    
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    for (int i =0; i<100; i++) {
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            static void(^testMethod)(int);
            
//            [lock lock];// 1 - crash
            [recursiveLock lock];
            
            testMethod = ^(int value) {
                
                // 使用 @synchronized 簡單方便
//                @synchronized (self) {
//                    if (value > 0) {
//                        NSLog(@"當(dāng)前 value = %d",value);
//                        testMethod(value - 1);
//                    }
//                };
                
//                [lock lock];// 2 - 堵死
            
                if (value > 0) {
                    NSLog(@"當(dāng)前 value = %d",value);
                    testMethod(value - 1);
                }
//                [lock unlock];// 1 - crash
//                [lock unlock];// 2 - 堵死
                [recursiveLock unlock];
            };
            
//            [lock lock];// 2 - 正常 - 但它造成的堵塞情況太嚴(yán)重
            testMethod(10);
//            [lock unlock];// 2 - 正常
            
        });
    }
}

NSRecursiveLock遞歸鎖和NSLock同是基于pthread_mutex封裝醇蝴,2者不同處主要在init設(shè)置:
NSRecursiveLockinit 類型設(shè)置標(biāo)記為 pthread_mutex_recursive:

    public override init() {
        super.init()
#if os(Windows)
        InitializeCriticalSection(mutex)
        InitializeConditionVariable(timeoutCond)
        InitializeSRWLock(timeoutMutex)
#else
#if CYGWIN
        var attrib : pthread_mutexattr_t? = nil
#else
        var attrib = pthread_mutexattr_t()
#endif
        withUnsafeMutablePointer(to: &attrib) { attrs in
            pthread_mutexattr_init(attrs)
            // 類型設(shè)置標(biāo)記為遞歸鎖: pthread_mutex_recursive
            pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
            pthread_mutex_init(mutex, attrs)
        }
#if os(macOS) || os(iOS)
        pthread_cond_init(timeoutCond, nil)
        pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
    }

遞歸鎖的使用宣肚,在循環(huán)嵌套場景中比較適用,而NSRecursiveLock遞歸鎖和@synchronized鎖類似悠栓,使用上個(gè)人更傾向于@synchronized霉涨,適用便利性更強(qiáng)按价。

3、NSConditionNSConditionLock條件鎖

  • NSCondition對象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器:--> 生產(chǎn)消費(fèi)者模型
  1. 鎖的目的主要是 在檢測條件時(shí)保護(hù)數(shù)據(jù)源笙瑟,執(zhí)行 條件引發(fā)的任務(wù);
  2. 線程檢查器主要是 根據(jù)條件決定是否繼續(xù)運(yùn)行線程楼镐,即線程是否被阻塞。
  • NSConditionLock鎖往枷,一旦一個(gè)線程獲得鎖框产,其他線程一定等待,它的使用可以攜帶著條件错洁。

3.1秉宿、NSCondition 使用

示例代碼:

#pragma mark - NSCondition -
- (void)my_testConditon {
    
    _testCondition = [[NSCondition alloc] init];
    
    // 創(chuàng)建生產(chǎn)-消費(fèi)者
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self my_producer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self my_consumer];
        });
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self my_consumer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self my_producer];
        });
    }
}

- (void)my_producer {
    
     [_testCondition lock];
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
    [_testCondition signal];
    [_testCondition unlock];
}
- (void)my_consumer {
    
     // 線程安全
     [_testCondition lock];

//    while (self.ticketCount == 0) {
//        NSLog(@"等待 count %zd",self.ticketCount);
//    }
    if (self.ticketCount == 0) {// 這里 if 是截不住條件的
        NSLog(@"等待 count %zd",self.ticketCount);
        // 保證正常流程
        [_testCondition wait];
    }
    
    //注意消費(fèi)行為,要在等待條件判斷之后
    self.ticketCount -= 1;
    NSLog(@"消費(fèi)一個(gè) 還剩 count %zd ",self.ticketCount);
    [_testCondition unlock];
}

3.2屯碴、NSConditionLock 條件鎖的使用

示例代碼:

- (void)my_testConditonLock {
    
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
         // 1 和 創(chuàng)建的 conditionLock 條件值對比 - 1!=2描睦,不執(zhí)行       
         [conditionLock lockWhenCondition:1];
         NSLog(@"線程 1");
         [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        // 2 和 創(chuàng)建的 conditionLock 條件值對比 - 2=2,執(zhí)行
        [conditionLock lockWhenCondition:2];
        NSLog(@"線程 2");
        [conditionLock unlockWithCondition:1];
        // 這里 condition 條件值設(shè)為了 1导而,會觸發(fā)通知到在等待的線程1可以執(zhí)行
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       
       [conditionLock lock];
       NSLog(@"線程 3");
       [conditionLock unlock];
    });
// 執(zhí)行結(jié)果:先線程2后線程1酌摇,3和2執(zhí)行順序不定
}


* Tip: 原子鎖 - atomic

// 原子鎖模擬代碼
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    @synchronized (self) {
        // 加鎖,保證同一時(shí)間只有一條線程可進(jìn)行寫入 
        _name = name;
    }
}
  • atomic原子鎖特點(diǎn)
    1.默認(rèn)的屬性嗡载;
    2.只在屬性的 setter方法中添加了自旋鎖 spin,保證在同一時(shí)間仍稀,只有一條線程可進(jìn)行寫操作 --> 單寫多讀. setter 分析
    3.一定程度保證科線程安全洼滚,但耗費(fèi)大量資源。
  • nonatomic
    1.非原子屬性技潘,沒有鎖遥巴,非線程安全;
    2.性能高于atomic享幽;

建議
1. iOS開發(fā)中多用nonatomic铲掐。因?yàn)?code>atomic耗費(fèi)性能有點(diǎn)高,大概是nonatomic至少10倍值桩,客戶端壓力太大摆霉。
2. 盡量避免多線程搶奪同一塊資源。若要搶奪資源并保證線程安全奔坟,可在相應(yīng)位置單獨(dú)加鎖携栋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咳秉,隨后出現(xiàn)的幾起案子婉支,更是在濱河造成了極大的恐慌,老刑警劉巖澜建,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件向挖,死亡現(xiàn)場離奇詭異蝌以,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)何之,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門跟畅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帝美,你說我怎么就攤上這事碍彭。” “怎么了悼潭?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵庇忌,是天一觀的道長。 經(jīng)常有香客問我舰褪,道長皆疹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任占拍,我火速辦了婚禮略就,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晃酒。我一直安慰自己表牢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布贝次。 她就那樣靜靜地躺著崔兴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛔翅。 梳的紋絲不亂的頭發(fā)上敲茄,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機(jī)與錄音山析,去河邊找鬼堰燎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笋轨,可吹牛的內(nèi)容都是我干的秆剪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爵政,長吁一口氣:“原來是場噩夢啊……” “哼鸟款!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茂卦,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤何什,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后等龙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體处渣,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伶贰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罐栈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黍衙。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荠诬,靈堂內(nèi)的尸體忽然破棺而出琅翻,到底是詐尸還是另有隱情,我是刑警寧澤柑贞,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布方椎,位于F島的核電站,受9級特大地震影響钧嘶,放射性物質(zhì)發(fā)生泄漏棠众。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一有决、第九天 我趴在偏房一處隱蔽的房頂上張望闸拿。 院中可真熱鬧,春花似錦书幕、人聲如沸新荤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迟隅。三九已至,卻和暖如春励七,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奔缠。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工掠抬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人校哎。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓两波,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闷哆。 傳聞我的和親對象是個(gè)殘疾皇子腰奋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容