锁底层分析

186 阅读4分钟

synchronized

  • @synchronized 代码块 -> 到底是什么 怎么研究  汇编 + xcrun

  • 1: 枷锁的效果

  • 2: 递归可重入

  • 3: 什么结构 image.png

  • 终端cd + 文件路径 image.png

  • 终端 xcrun -sdk iphoneos clang -arch arm64e -rewrite-objc main.m

  • 通过将main.m 文件生成一个 main.cpp文件

  • 发现这段代码 image.png

  • 转化后就是这样的 image.png objc_sync_enter(_sync_obj); objc_sync_exit(_sync_obj)

全局断点测试研究

  • 添加全局断点 image.png

  • 定位到源码区域 libobjc.A.dylib objc_sync_enter:

image.png

  • 打开objc源码版本为objc4-818.2
  • objc_sync_enter image.png 传入参数obj如果没有传参则报错

image.png 加锁

  • objc_sync_exit image.png 这里也是需要传入参数否则do noting

image.png 这里解锁

SyncData

  • 是一个单项链表的数据结构 image.png

DisguisedPtr<objc_object> object;

image.png 无符号长整形转成value形式,方便计算

  • threadCount 多条线程访问
  • recursive_mutex_t mutex; 加锁

SyncData* data = id2data(obj, ACQUIRE);

image.png 这两处是线程局部存储

image.png 这里内存对齐创建了一段内存空间,这里的加锁和解锁是保护这段内存空间的加锁和解锁与外部的加锁和解锁不一样

  • spinlock_t *lockp = &LOCK_FOR_OBJ(object); 通过宏定义获取的一个锁

image.png

  • sDataLists是一个全局的哈希表结构,表中存放的时SyncList的结构体数据,哈希表中存放单向链表。

image.png

  • 流程分析
  • 第一步创建一个新的SyncData加入到list中 image.png 使用头插法完成一个链表结构

image.png

  • 取出上一个存储的syncdata,如果与当前相同则进入递归加锁和解锁,否则不操作。

image.png

  • 这里也是一样的如果当前存放的对象与之前的对象不一样,listp是空的,所以也不进行这个操作,进入创建新对象加入到list中。
  • 如果对象相同 image.png 加锁数量++或--,如果lockCount==0则,处理这条线程的代码空间。

image.png OSAtomicIncrement32Barrier(&result->threadCount);对象的线程内存屏障加1,这里是线程不同时才会进入的,这样就能解释一个对象被多个线程加锁。

  • threadCount:表示这个对象被多少条线程获取。
  • lockCount:表示这个对象被锁多少次。
  • 多线程可递归。

Sync 总结

  • 1: sync 哈希表 - 拉链法 syncData 

  • 2: sDatalist array synclist (objc) 

  • 3: objc_sync_enter / exit 对称 递归锁

  • 4: 两种存储 : tls / cache 

  • 5: 第⼀次 syncData 头插法 链表 标记 thracount = 1 

  • 6: 判断是不是同⼀个对象

  • 7: TLS -> lock ++ 

  • 8: TLS 找不到 sync threadCount ++ 

  • 9: lock — threadCount— 

Synchronized : 可重⼊ 递归 多线程

  • 1: TLS 保障 threadCount 多少条线程对这个锁对象加锁

  • 2: lock ++ 进来多少次

锁的分类

  • ⾃旋锁:线程反复检查锁变量是否可⽤。由于线程在这⼀过程中保持执⾏,因此是⼀种忙等待。⼀旦获取了⾃旋锁,线程会⼀直保持该锁,直⾄显式释放⾃旋锁。 ⾃旋锁避免了进程上下⽂的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

  • 互斥锁:是⼀种⽤于多线程编程中,防⽌两条线程同时对同⼀公共资源(⽐如全局变量)进⾏读写的机制。该⽬的通过将代码切⽚成⼀个⼀个的临界区⽽达成

  • 这⾥属于互斥锁的有:

• NSLock 

• pthread_mutex 

• @synchronized

  • 小案例 NSRecursiveLock
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;
#pragma mark -- NSRecursiveLock

- (void)lg_testRecursive{
        
 
    NSLock *lock = [[NSLock alloc] init];
    // @synchronized -> recursiveLock (多线程性) -> lock 递归性
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
//                [self.recursiveLock lock];
                @synchronized (self) {
                    if (value > 0) {
                        NSLog(@"current value = %d",value);
                        testMethod(value - 1);
                    }
//                [self.recursiveLock unlock];
                }
            };
           // [lock lock];
            testMethod(10);
           // [lock unlock];
        });
    }
}

NSCondition

NSCondition 的对象实际上作为⼀个锁和⼀个线程检查器:锁主要为了当检测条件时保护数据源,执⾏条件引发的任务;线程检查器主要是根据条件决定是否继续运⾏线程,即线程是否被阻塞

1: [condition lock];//⼀般⽤于多线程同时访问、修改同⼀个数据源,保证在同⼀时间内数据源只被访问、修改⼀次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问

2:[condition unlock];//与lock 同时使⽤

3:[condition wait];//让当前线程处于等待状态

4:[condition signal];//CPU发信号告诉线程不⽤在等待,可以继续执⾏

  • 小案例
#pragma mark -- 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];
}