OC底层探索(二十二)八大锁

OC底层文章汇总

锁的种类

在OC中锁分为互斥锁自旋锁两种。

互斥锁

  • 用于保护临界区,确保同一时间,只有一条线程能够执行

  • 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象

  • 加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠

针对互斥锁,还需要注意以下几点:

  • 互斥锁的锁定范围,应该尽量小,锁定范围越大效率越差

  • 能够加锁的任意 NSObject对象

  • 锁对象一定要保证所有的线程都能够访问

自旋锁

  • 自旋锁互斥锁类似,但它是通过休眠使线程阻塞,而是在获取锁之前一直处于忙等(即原地打转,称为自旋)阻塞状态

  • 使用场景:锁持有时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic,本身就有一把自旋锁

  • 加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能

互斥锁与自旋锁的异同

  • 同:

    • 在同一时间,保证了只有一条线程执行任务,即保证了相应同步的功能
  • 不同:

    • 互斥锁:发现其他线程执行,当前线程 休眠(即就绪状态),进入等待执行,即挂起。一直等其他线程打开之后,然后唤醒执行

    • 自旋锁:发现其他线程执行,当前线程 一直询问(即一直访问),处于忙等状态,耗费的性能比较

  • 使用场景:

    • 根据任务复杂度区分,使用不同的锁,但判断不全时,更多是使用互斥锁去处理 当前的任务状态比较短小精悍时,用自旋锁

    • 反之的,用互斥锁

八大锁

  • 锁的性能数据

在这里插入图片描述

  • 属于互斥锁:

    • @synchronized
    • pthread_mutex
    • NSLock
    • NSCondition
  • 属于条件锁

    • NSCondition
    • NSConditionLock
  • 属于自旋锁

    • OSSpinLock
    • atomic

图中锁的性能从高到底依次是:OSSpinLock(自旋锁) -> dispatch_semaphone(信号量) -> pthread_mutex(互斥锁) -> NSLock(互斥锁) -> NSCondition(条件锁) -> pthread_mutex(recursive 互斥递归锁) -> NSRecursiveLock(递归锁) -> NSConditionLock(条件锁) -> synchronized(互斥锁)

1. @synchronized

@synchronized是递归互斥锁

1.1 @synchronized的使用

  • 新建三个异步线程分别调用saleTicket
  • saleTicket中对属性ticketCount进行写操作。
- (void)lg_testSaleTicket{
    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{
    // 加锁 - 线程安全
    @synchronized (self) {
        
        if (self.ticketCount > 0) {
            self.ticketCount--;
            sleep(0.1);
            NSLog(@"当前余票还剩:%ld张",self.ticketCount);
            
        }else{
            NSLog(@"当前车票已售罄");
        }

    }

}
  • 打印输出
    在这里插入图片描述

1.2 源码分析

1. 如何查找源码文件
  • 在main.m文件中添加一个@synchronized,clang查看一下源码
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);
}
  • 就会发现编译成了objc_sync_enter_sync_exit
    在这里插入图片描述
  • 在项目中通过汇编 debug => debug workflow => always show … 跟流程,并在 objc_sync_enter 打一个断点
    在这里插入图片描述
  • 摁住control + step into,进入底层汇编,就会发现底层的实现是在libobjc.A.dylib
    在这里插入图片描述
  • 在官网下载objc4-718查看源码
objc_sync_enter分析
  • 查看objc_sync_enter

    • 判断obj是否为空

      • part1: 不为空执行id2data(obj, ACQUIRE);
      • part2: 为空执行objc_sync_nil
// 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;
}
part1: 不为空执行id2data(obj, ACQUIRE);
  • 查看SyncData结构

    • SyncData* nextData => 下一个节点
    • object => 被锁对象
    • threadCount => 几个线程被锁住
    • recursive_mutex_t mutex => 封装的互斥锁
    • recursive_mutex_trecursive_mutex_tt类型的结构体
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;

👇

  • 查看id2data,代码可分为部分

    • 第一部分TLS线程局部存储缓存()中查找是否存在SyncData
    • 第二部分全局线程缓存列表中查找,执行第三部分 然后再执行第四部分done
    • 第三部分第一次进来,给SyncData赋值,将lockCount和threadCount都设置为1,执行done
    • 第四部分 done部分:并将对象存入TLS全局线程缓存列表中。
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
    // Check per-thread single-entry fast cache for matching object
    //第一部分
    //part1: 不是第一次进来,但是在同一个线程
    //在TLS线程局部存储缓存(栈)中查找是否存在SyncData
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {
            // Found a match in fast cache.
            uintptr_t lockCount;

            result = data;
            //获取lockCount
            lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
            //如果threadCount 和 lockCount都小于等于0,抛出异常
            if (result->threadCount <= 0  ||  lockCount <= 0) {
                _objc_fatal("id2data fastcache is buggy");
            }
			
			//why => 外部传入的ACQUIRE或者RELEASE
            switch(why) {
            //ACQUIRE lockCount + 1
            case ACQUIRE: {
                lockCount++;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;
            }
            //RELEASE lockCount - 1
            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

    // Check per-thread cache of already-owned locks for matching object
    //第二部分
    //part2: 不是第一次进来,而且不是同一线程
    //在全局线程缓存中查找
    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;

            // 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->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.
   //第三部分
    lockp->lock();
 //park3:缓存中没有发现,没有加锁,此第一次加锁
    {
        SyncData* p;
        SyncData* firstUnused = NULL;
        //循环listp是否有值
        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;
        }
    
    	// 如果SyncData为空,进入done中执行
        // no SyncData currently associated with object
        if ( (why == RELEASE) || (why == CHECK) )
            goto done;
    
        // an unused one was found, use it
        //SyncData不为空,将threadCount置位1
        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));
    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中设置值,将result和lockCount都设置进去
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        } else 
#endif
        {
            // 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;
}
done流程
  • 判断是否支持SUPPORT_DIRECT_THREAD_KEYS,如果支持执行KVC,就向tls中设置值,

    • result设置进去,SYNC_DATA_DIRECT_KEY
    • lockCount = 1设置到,SYNC_COUNT_DIRECT_KEY
  • 不支持SUPPORT_DIRECT_THREAD_KEYS,那么就向线程缓存中存一份

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中设置值,将result和lockCount都设置进去
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        } else 
#endif
        {
            // 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;
第一种情况:第一次进来没有加锁

执行第三部分,在执行第四部分

  • 第一次进来,在两个缓存中都没有找到,那么就会执行第三部分的流程

    • 循环listp是否有值,如果值就将此值赋给result,并对threadCount++,跳转到done流程(显然第一次进入时没有值的哦)
    • 如果SyncData,进入done中执行
    • SyncData ,第一次进来没有找到,将threadCount置位1
lockp->lock();
 //park3:缓存中没有发现,没有加锁,此第一次加锁
    {
        SyncData* p;
        SyncData* firstUnused = NULL;
        //循环listp是否有值
        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;
            }
            //没有找到就将p赋值给firstUnused
            if ( (firstUnused == NULL) && (p->threadCount == 0) )
                firstUnused = p;
        }
    
    	// 如果SyncData为空,进入done中执行
        // no SyncData currently associated with object
        if ( (why == RELEASE) || (why == CHECK) )
            goto done;
    
        // an unused one was found, use it
        //SyncData不为空,将threadCount置位1
        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));
    result->object = (objc_object *)object;
    result->threadCount = 1;
    new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
    result->nextData = *listp;
    *listp = result;
    

第二种情况:不是第一次进来,但是是同一个线程

执行第一部分

  • TLS线程局部存储缓存()中查找是否存在SyncData

  • 获取lockCount,如果threadCount 和 lockCount都小于等于0,抛出异常

  • 判断whyACQUIRE还是RELEASE

    • ACQUIRElockCount++

    • RELEASE: lockCount--

  • TLS再存一下

 //part1: 不是第一次进来,但是在同一个线程
    //在TLS线程局部存储缓存(栈)中查找是否存在SyncData
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {
            // Found a match in fast cache.
            uintptr_t lockCount;

            result = data;
            //获取lockCount
            lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
            //如果threadCount 和 lockCount都小于等于0,抛出异常
            if (result->threadCount <= 0  ||  lockCount <= 0) {
                _objc_fatal("id2data fastcache is buggy");
            }
			
			//why => 外部传入的ACQUIRE或者RELEASE
            switch(why) {
            //ACQUIRE lockCount + 1
            case ACQUIRE: {
                lockCount++;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;
            }
            //RELEASE lockCount - 1
            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;
        }
    }
第三种情况:不是第一次进来,也不是同一个线程

先执行第二部分,在执行第三部分,最后执行第四部分

  • 全局线程缓存中查找,找到item,将lockCount++
  • 再执行第三部分,将threadCount++
  • 最后执行第四部分
 //第二部分
    //part2: 不是第一次进来,而且不是同一线程
    //在全局线程缓存中查找
    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;

            // 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->list[i] = cache->list[--cache->used];
                    // atomic because may collide with concurrent ACQUIRE
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }

            return result;
        }
    }
part2: 为空执行objc_sync_nil
  • 查看objc_sync_nil,进入BREAKPOINT_FUNCTION,传入参数是void objc_sync_nil(void)
BREAKPOINT_FUNCTION(
    void objc_sync_nil(void)
);

👇

  • 查看BREAKPOINT_FUNCTION,传入参数prototype,而它执行了asm("");什么都没操作。
#   define BREAKPOINT_FUNCTION(prototype)                             \
    OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
    prototype { asm(""); }

1.3 坑点

  • 下面代码这样写,会有什么问题?
 - (void)cjl_testSync{
    _testArray = [NSMutableArray array];
    for (int i = 0; i < 200000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self.testArray) {
                self.testArray = [NSMutableArray array];
            }
        });
    }
}
  • 运行结果是 程序崩溃

崩溃的主要原因是testArray在某一瞬间变成了nil,从@synchronized底层流程知道,如果加锁的对象成了nil,是锁不住的,相当于下面这种情况,block内部不停的retain、release,会在某一瞬间上一个还未release,下一个已经准备release,这样会导致野指针的产生

在这里插入图片描述

  • 可以根据下面的代码,打开edit scheme -> run -> Diagnostics中勾选Zombie Objects,来查看是否是僵尸对象,结果如下所示
_testArray = [NSMutableArray array];
for (int i = 0; i < 200000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        _testArray = [NSMutableArray array];
    });
}

在这里插入图片描述

  • 我们一般使用@synchronized (self),主要是因为_testArray的持有者是self

注意:野指针 vs 过渡释放

  • 野指针:是指由于过渡释放产生的指针还在进行操作

  • 过渡释放:每次都会retain 和 release

1.4 小结

  • 由于@synchronized,在加锁时,查询了两张表,所以比较耗费性能
  • 全局加锁线程缓存:存储的所有的加锁线程,根据’对象’找到’加锁对象’。
  • TLS线程局部存储缓存():存储的是加锁的对象(必须一个对象)
  • 使用方便易写
  • tls和cache表结构

在这里插入图片描述

2. atomic 原子锁 & nonatomic 非原子锁

atomic 和 nonatomic主要用于属性的修饰,以下是相关的一些说明:

  • atomic是原子属性,是为多线程开发准备的,是默认属性

    • 仅仅在属性的 setter 方法中,增加了锁(自旋锁),能够保证同一时间,只有一条线程对属性进行操作

    • 同一时间 单(线程)写 多(线程)读的线程处理技术

    • Mac开发中常用

  • nonatomic 是非原子属性

    • 没有锁!性能高!

    • 移动端开发常用

atomic与nonatomic 的区别

  • nonatomic

    • 非原子属性

    • 非线程安全适合内存小的移动设备

  • atomic

    • 原子属性(线程安全),针对多线程设计的,默认值

    • 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)

    • atomic 本身就有一把锁(自旋锁) 单写多读:单个线程写入,多个线程可以读取

    • 线程安全需要消耗大量的资源

  • iOS 开发的建议

    • 所有属性都声明为 nonatomic

    • 尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

3. 信号量

请查看文章OC底层探索(二十一)GCD异步、GCD同步、单例、信号量、调度组、栅栏函数等底层分析

4. NSLock

4.1 NSLock的使用

NSLock 是一把互斥锁,不可在递归循环中使用

    // NSLock 多 非常简单
   NSLock *lock = [[NSLock alloc] init];
   [lock lock];
   [lock unlock];

4.2 NSLock源码

由于NSLock是在foundation中,OC是没有开源的

在这里插入图片描述

所以看swift源码即可:

NSLock是对pthread的封装

在这里插入图片描述

  • lock: 直接调用互斥锁
  • unlock: 调用解锁,并将当前解锁的状态广播出去
  • NSLock中的init方法中,还做了一些其他操作,所以在使用NSLock时需要使用init初始化

5、NSRecursiveLock

NSRecursiveLock 是递归互斥锁

5.1 NSRecursiveLock 的使用

NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
[recursiveLock lock];
[recursiveLock unlock];

5.2 NSRecursiveLock 源码

  • NSRecursiveLock在底层也是对pthread_mutex的封装,可以通过swift的Foundation源码查看

在这里插入图片描述

  • 对比NSLockNSRecursiveLock,其底层实现几乎一模一样,区别在于init时,NSRecursiveLock有一个标识PTHREAD_MUTEX_RECURSIVE,而NSLock默认

在这里插入图片描述

  • 递归锁主要是用于解决一种嵌套形式,其中循环嵌套居多

6. NSCondition

消费者 - 生产者 模式加锁

-NSCondition是一个条件锁,在日常开发中使用较少,与信号量有点相似:线程1需要满足条件1才会往下走,否则会堵塞等待,知道条件满足。经典模型是生产消费者模型

  • NSCondition的对象实际上作为一个锁一个线程检查器

  • 主要 为了当检测条件时保护数据源,执行条件引发的任务

  • 线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞

6.1 NSCondition的使用

- (void)lg_testConditon{
    
    _testCondition = [[NSCondition alloc] init];
    //创建生产-消费者
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
    }
}

- (void)lg_producer{
    [_testCondition lock]; // 操作的多线程影响
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生产一个 现有 count %zd",self.ticketCount);
    [_testCondition signal]; // 信号
    [_testCondition unlock];
}

- (void)lg_consumer{
 
     [_testCondition lock];  // 操作的多线程影响
    if (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
        [_testCondition wait];
    }
    //注意消费行为,要在等待条件判断之后
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
     [_testCondition unlock];
}

6.2 源码分析

通过swift的Foundation源码查看NSCondition的底层实现

  • 其底层也是对下层pthread_mutex的封装

  • NSCondition是对mutexcond的一种封装(cond就是用于访问和操作特定类型数据的指针)

  • wait操作会阻塞线程,使其进入休眠状态,直至超时

  • signal操作是唤醒一个正在休眠等待的线程

  • broadcast唤醒所有正在等待的线程

open class NSCondition: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
    //初始化
    public override init() {
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
    }
    //析构
    deinit {
        pthread_mutex_destroy(mutex)
        pthread_cond_destroy(cond)

        mutex.deinitialize(count: 1)
        cond.deinitialize(count: 1)
        mutex.deallocate()
        cond.deallocate()
    }
    //加锁
    open func lock() {
        pthread_mutex_lock(mutex)
    }
    //解锁
    open func unlock() {
        pthread_mutex_unlock(mutex)
    }
    //等待
    open func wait() {
        pthread_cond_wait(cond, mutex)
    }
    //等待
    open func wait(until limit: Date) -> Bool {
        guard var timeout = timeSpecFrom(date: limit) else {
            return false
        }
        return pthread_cond_timedwait(cond, mutex, &timeout) == 0
    }
    //信号,表示等待的可以执行了
    open func signal() {
        pthread_cond_signal(cond)
    }
    //广播
    open func broadcast() {
        // 汇编分析 
        pthread_cond_broadcast(cond) // wait  signal
    }
    open var name: String?
}

7、NSConditionLock

NSConditionLock条件锁,一旦一个线程获得锁,其他线程一定等待

相比NSConditionLock而言,NSCondition使用比较麻烦,所以推荐使用NSConditionLock,其使用如下

NSConditionLock的使用

  • NSConditionLock的API
//初始化
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];

//表示 conditionLock 期待获得锁,如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
[conditionLock lock]; 

//表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且 没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的 完成,直至它解锁。
[conditionLock lockWhenCondition:A条件]; 

//表示释放锁,同时把内部的condition设置为A条件
[conditionLock unlockWithCondition:A条件]; 

// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函 数的目的在于可以实现两种状态下的处理
return = [conditionLock lockWhenCondition:A条件 beforeDate:A时间];

//其中所谓的condition就是整数,内部通过整数比较条件
  • 例子
 - (void)lg_testConditonLock{
    // 信号量
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
         [conditionLock lockWhenCondition:1]; // conditoion = 1 内部 Condition 匹配
        // -[NSConditionLock lockWhenCondition: beforeDate:]
        NSLog(@"线程 1");
         [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
       
        [conditionLock lockWhenCondition:2];
        sleep(0.1);
        NSLog(@"线程 2");
        // self.myLock.value = 1;
        [conditionLock unlockWithCondition:1]; // _value = 2 -> 1
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       
       [conditionLock lock];
       NSLog(@"线程 3");
       [conditionLock unlock];
    });
}
  • 线程 1调用[NSConditionLock lockWhenCondition:],此时此刻因为不满足当前条件,所以会进入
    waiting 状态,当前进入到 waiting 时,会释放当前的互斥锁

  • 此时当前的线程 3 调用[NSConditionLock lock:],本质上是调用[NSConditionLock lockBeforeDate:],这里不需要比对条件值,所以线程 3打印

  • 接下来线程 2执行[NSConditionLock lockWhenCondition:],因为满足条件值,所以线程2
    会打印,打印完成后会调用[NSConditionLock unlockWithCondition:],这个时候将value 设置为
    1,并发送boradcast, 此时线程 1接收到当前的信号,唤醒执行并打印。

  • 自此当前打印为 线程 3->线程 2 -> 线程 1

  • [NSConditionLock lockWhenCondition:];这里会根据传入的 condition 值和 Value
    值进行对比,如果不相等,这里就会阻塞,进入线程池,否则的话就继续代码执行[NSConditionLock unlockWithCondition:]: 这里会先更改当前的value 值,然后进行广播,唤醒当前的线程

7.2 源码分析

-NSConditionLockNSCondition的封装

  • NSConditionLock可以设置锁条件,即condition值,而NSCondition只是信号的通知

  • conditionvalue 不相等时,线程等待,超时解锁;反之,解锁

open class NSConditionLock : NSObject, NSLocking {
    internal var _cond = NSCondition()
    internal var _value: Int
    internal var _thread: _swift_CFThreadRef?
    
    public convenience override init() {
        self.init(condition: 0)
    }
    
    public init(condition: Int) {
        _value = condition
    }

    open func lock() {
        let _ = lock(before: Date.distantFuture)
    }

    open func unlock() {
        _cond.lock()
        _thread = nil
        _cond.broadcast()
        _cond.unlock()
    }
    
    open var condition: Int {
        return _value
    }

    open func lock(whenCondition condition: Int) {
        let _ = lock(whenCondition: condition, before: Date.distantFuture)
    }

    open func `try`() -> Bool {
        return lock(before: Date.distantPast)
    }
    
    open func tryLock(whenCondition condition: Int) -> Bool {
        return lock(whenCondition: condition, before: Date.distantPast)
    }

    open func unlock(withCondition condition: Int) {
        _cond.lock()
        _thread = nil
        _value = condition
        _cond.broadcast()
        _cond.unlock()
    }

    open func lock(before limit: Date) -> Bool {
        _cond.lock()
        while _thread != nil {
            if !_cond.wait(until: limit) {
                _cond.unlock()
                return false
            }
        }
         _thread = pthread_self()
        _cond.unlock()
        return true
    }
    
    open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
        _cond.lock()
        while _thread != nil || _value != condition {
            if !_cond.wait(until: limit) {
                _cond.unlock()
                return false
            }
        }
        _thread = pthread_self()
        _cond.unlock()
        return true
    }
    
    open var name: String?
}

8.pthread_mutex

pthread_mutex就是互斥锁本身,当锁被占用,其他线程申请锁时,不会一直忙等待,而是阻塞线程并睡眠

  • 使用
// 导入头文件
#import <pthread.h>

// 全局声明互斥锁
pthread_mutex_t _lock;

// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);

// 加锁
pthread_mutex_lock(&_lock);
// 这里做需要线程安全操作
// 解锁 
pthread_mutex_unlock(&_lock);

// 释放锁
pthread_mutex_destroy(&_lock);

总结

  • OSSpinLock自旋锁由于安全性问题,在iOS10之后已经被废弃,其底层的实现用os_unfair_lock替代

    • 使用OSSpinLock及所示,会处于忙等待状态

    • os_unfair_lock是处于休眠状态

  • atomic原子锁自带一把自旋锁,只能保证setter、getter时的线程安全,在日常开发中使用更多的还是nonatomic修饰属性

    • atomic:当属性在调用settergetter方法时,会加上自旋锁osspinlock,用于保证同一时刻只能有一个线程调用属性的读或写避免属性 读写不同步 的问题。由于是底层编译器自动生成的互斥锁代码,会导致效率相对较

    • nonatomic:当属性在调用settergetter方法时,不会加上自旋锁,即线程不安全。由于编译器不会自动生成互斥锁代码,可以提高效率

  • @synchronized在底层维护了一个哈希表进行线程data的存储,通过链表表示可重入(即嵌套)的特性,虽然性能较,但由于简单好用,使用频率很高

  • NSLockNSRecursiveLock底层是对pthread_mutex的封装

  • NSConditionNSConditionLock条件锁,底层都是对pthread_mutex的封装,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore类似

使用场景

  • 如果只是简单的使用,例如涉及线程安全,使用NSLock即可

  • 如果是循环嵌套推荐使用@synchronized,主要是因为使用递归锁的 性能 不如使用@synchronized的性能(因为在synchronized中无论怎么重入,都没有关系,而NSRecursiveLock可能会出现崩溃现象)

  • 循环嵌套中,如果对递归锁掌握的很好,则建议使用递归锁,因为性能好

  • 如果是循环嵌套,并且还有多线程影响时,例如有等待死锁现象时,建议使用@synchronized

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值