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阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容