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-
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值