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

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

image.png

一、鎖的分類

關于同步的Apple文檔

image.png

基本的鎖就包括了2大類:自旋鎖 互斥鎖.
其他的比如條件鎖遞歸鎖信號量都是上層的封裝實現.

  • 讀寫鎖 - (線程)讀單(線程)
    實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。讀寫鎖相對于自旋鎖而言,能提高并發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯 CPU 數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。在讀寫鎖保持期間也是搶占失效的。
    1. 如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則寫者必須自旋在那里,直到沒有任何寫者或讀者;
    2. 如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那里,直到寫者釋放該讀寫鎖。

1、自旋鎖

  1. 線程反復檢查鎖變量是否可用。由于線程在這一過程中保持執行, 因此是一種忙等待;
  2. 一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋放自旋鎖;
  3. 自旋鎖避免了進程上下文的調度開銷,因此對于線程只會阻塞很短時間的場合是有效的,小而精 的任務;
  4. 因 一直檢查詢問鎖是否打開可用,耗費性能比較高

2、互斥鎖

  1. 是一種多線程編程中,防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。它通過將代碼切片成一個一個的臨界區 而實現。
  2. 保證同一時間只有一條線程可進行某執行任務 - 類似保證了同步的功能。
    當發現別的線程正在操作任務,當前線程獲取互斥鎖失敗,當前線程進入休眠 (就緒狀態 - 等待被調度執行) --> 一直等到其他線程打開鎖之后 --> 喚起 執行
  3. 常見的互斥鎖 - 互斥鎖分為遞歸和非遞歸鎖
    3.1 NSLock
    3.2 @synchronized
    3.3 pthread_mutex

2.1) 遞歸鎖

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

3、條件鎖

  1. 條件變量。當進程的某些資源要求不滿足時就進入休眠,也就
    是鎖住了。當資源被分配到了,條件鎖打開,進程繼續運行。
  2. 常見的條件鎖:
    2.1 NSCondition
    2.2 NSConditionLock

4、信號量 semaphore - dispatch_semaphore

  1. 信號量是一種更高級的同步機制,互斥鎖可以說是
    semaphore在僅取值0/1時的特例。
  2. 信號量可以有更多的取值空間,用來實現更加復雜的同步,而不單單是線程間互斥。

二、鎖的原理分析

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

- (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(@"當前余票還剩:%ld張",self.ticketCount);
        
    }else{
        NSLog(@"當前車票已售罄");
    }
}

運行程序,輸出結果如下:

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

由上示例,余票票數是有問題的,對于多線程操作,數據的安全性必須考慮。

1、@synchronized 原理

給上面的示例代碼添加@synchronized鎖如下,再次運行工程:

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

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

由上,余票的數據是正確的,線程安全已解決。@synchronized是如何實現線程安全的呢?

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

clang -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk /目標文件路徑/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")));
        
        { // 代碼塊區域
            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. 運行工程,打開debug匯編調試也可找到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

通過匯編,跳進到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: --> 鏈表結構

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);
    }
};

由上源碼可驗證@synchronized是個遞歸互斥鎖。

1.1.2、@synchronized的實現

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
    // 檢查每個線程 單條目快速緩存 是否匹配對象
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {// 當前的 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鎖的次數 --> 可重復被鎖
                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
    // 檢查 已擁有鎖 的每個線程緩存 是否匹配對象
    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、對象所對應的鏈表 遍歷查找
    {
        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;
            }
            // 第一次進來
            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.
    // 沒有,創建新鏈表插入哈希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結構:

image.png

  • @synchronized邏輯流程:
    對象objc@synchronized:
    1. 首先在棧存空間查找,匹配上:lockCount++并返回;
    2. 沒有繼續在全局的哈希list緩存查找,匹配到:lockCount++并返回;
    3. 沒有找到,則開始遍歷當前對象objc所對應的鏈表遍歷查,找到,goto done,保存到緩存;
    4. 沒找到,創建結點給新鏈表,
      4.1 若支持線程key - SUPPORT_DIRECT_THREAD_KEYS,通過tls_set_direct(k,value) - 以 KVC方式保存到tls(tls:本地局部的線程緩存);
      tls: 線程局部存儲(Thread Local Storage,TLS):是操作系統為線
      程單獨提供的私有空間,通常只有有限的容量

      4.2 不支持,將新鏈表保存到緩存,即 開辟空間將其存到哈希list中。

*問題:
@synchronized性能差原因由其實現原理也可知,鏈表的查找速度很慢,盡管做了緩存,但其速度仍是相較慢的。
為何@synchronized性能那么低還要用它呢?
--> 使用方便,封裝性高不用關心內部加解鎖。
* 注意點:
@synchronized()使用時,需要鎖住的對象要注意其生命周期,一般常見的是鎖self,其原因是對象的生命是和所在的self,并非都用self
--> 我們在使用@synchronized()時考慮保證要鎖住的對象其生命正常即可。
示例代碼如下:

- (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) {// 它可能在某時刻為nil,@synchronized鎖對nil不進行任何操作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源碼進行探究。

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 的創建必須 init,下面代碼可說明原因:
        // 條件數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進行了一層封裝了,NSLock鎖的性能次于pthread_mutex鎖一點點。

* 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(@"當前 value = %d",value);
//                        testMethod(value - 1);
//                    }
//                };
                
//                [lock lock];// 2 - 堵死
            
                if (value > 0) {
                    NSLog(@"當前 value = %d",value);
                    testMethod(value - 1);
                }
//                [lock unlock];// 1 - crash
//                [lock unlock];// 2 - 堵死
                [recursiveLock unlock];
            };
            
//            [lock lock];// 2 - 正常 - 但它造成的堵塞情況太嚴重
            testMethod(10);
//            [lock unlock];// 2 - 正常
            
        });
    }
}

NSRecursiveLock遞歸鎖和NSLock同是基于pthread_mutex封裝,2者不同處主要在init設置:
NSRecursiveLockinit 類型設置標記為 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)
            // 類型設置標記為遞歸鎖: 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
    }

遞歸鎖的使用,在循環嵌套場景中比較適用,而NSRecursiveLock遞歸鎖和@synchronized鎖類似,使用上個人更傾向于@synchronized,適用便利性更強。

3、NSConditionNSConditionLock條件鎖

  • NSCondition對象實際上作為一個鎖和一個線程檢查器:--> 生產消費者模型
  1. 鎖的目的主要是 在檢測條件時保護數據源,執行 條件引發的任務;
  2. 線程檢查器主要是 根據條件決定是否繼續運行線程,即線程是否被阻塞。
  • NSConditionLock鎖,一旦一個線程獲得鎖,其他線程一定等待,它的使用可以攜帶著條件。

3.1、NSCondition 使用

示例代碼:

#pragma mark - NSCondition -
- (void)my_testConditon {
    
    _testCondition = [[NSCondition alloc] init];
    
    // 創建生產-消費者
    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(@"生產一個 現有 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];
    }
    
    //注意消費行為,要在等待條件判斷之后
    self.ticketCount -= 1;
    NSLog(@"消費一個 還剩 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 和 創建的 conditionLock 條件值對比 - 1!=2,不執行       
         [conditionLock lockWhenCondition:1];
         NSLog(@"線程 1");
         [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        // 2 和 創建的 conditionLock 條件值對比 - 2=2,執行
        [conditionLock lockWhenCondition:2];
        NSLog(@"線程 2");
        [conditionLock unlockWithCondition:1];
        // 這里 condition 條件值設為了 1,會觸發通知到在等待的線程1可以執行
    });

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


* Tip: 原子鎖 - atomic

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

建議
1. iOS開發中多用nonatomic。因為atomic耗費性能有點高,大概是nonatomic至少10倍,客戶端壓力太大。
2. 盡量避免多線程搶奪同一塊資源。若要搶奪資源并保證線程安全,可在相應位置單獨加鎖。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容