OC底层原理27-锁下

433 阅读7分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

一.锁的分类讲解

1.⾃旋锁:

自旋锁:你走 我等你回来 = 互斥(闲等) + 忙等 线程反复检查锁变量是否可⽤。由于线程在这⼀过程中保持执⾏,

因此是⼀种忙等待。⼀旦获取了⾃旋锁,线程会⼀直保持该锁,直⾄显式释

放⾃旋锁。 ⾃旋锁避免了进程上下⽂的调度开销,因此对于线程只会阻塞很

短时间的场合是有效的。

2.互斥锁:

互斥 + 同步 两条线程 我走 你不能走/按照相应的顺序 同步 = 互斥 + 顺序 是⼀种⽤于多线程编程中,防⽌两条线程同时对同⼀公共资源(⽐

如全局变量)进⾏读写的机制。该⽬的通过将代码切⽚成⼀个⼀个的临界区

⽽达成

这⾥属于互斥锁的有:

• NSLock

• pthread_mutex 

• @synchronized

条件锁:就是条件变量,当进程的某些资源要求不满⾜时就进⼊休眠,也就

是锁住了。当资源被分配到了,条件锁打开,进程继续运⾏

• NSCondition

• NSConditionLock 

递归锁:就是同⼀个线程可以加锁N次⽽不会引发死锁

• NSRecursiveLock 

• pthread_mutex(recursive) 

信号量(semaphore):是⼀种更⾼级的同步机制,互斥锁可以说是

semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,⽤来实

现更加复杂的同步,⽽不单单是线程间互斥。

• dispatch_semaphore

锁的归类

其实基本的锁就包括了三类 ⾃旋锁 互斥锁 读写锁,

其他的⽐如条件锁,递归锁,信号量都是上层的封装和实现!

3.读写锁

读写锁实际是⼀种特殊的互斥锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源

进⾏读访问,写者则需要对共享资源进⾏写操作。这种锁相对于⾃旋锁⽽⾔,能提⾼并发性,因为

在多处理器系统中,它允许同时有多个读者来访问共享资源,最⼤可能的读者数为实际的逻辑CPU

数。写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时

既有读者⼜有写者。在读写锁保持期间也是抢占失效的。

如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,

直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须

⾃旋在那⾥,直到写者释放该读写锁。

⼀次只有⼀个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁. 正

是因为这个特性,

当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.

当读写锁在读加锁状态时, 所有试图以读模式对它进⾏加锁的线程都可以得到访问权, 但是如果

线程希望以写模式对此锁进⾏加锁, 它必须直到所有的线程释放锁.

通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞

随后的读模式锁请求, 这样可以避免读模式锁⻓期占⽤, ⽽等待的写模式锁请求⻓期阻塞.

读写锁适合于对数据结构的读次数⽐写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写

模式锁住时意味着独占, 所以读写锁⼜叫共享-独占锁.

二.NSLockNSRecursiveLock的分析

案例1.NSLock

  NSLock *lock = [[NSLock alloc] init];
    for (int i = 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                [lock lock];
                if (value > 0) {
                    NSLog(@"current value = %d",value);
                    testMethod(value - 1);
                }
            };
            testMethod(10);
            [lock unlock]; 
        });
    }
 运行打印结果
 2021-08-19 21:19:27.916241+0800 003-NSLock分析[3229:3825300] current value = 10
 死锁

因为执行完testMethod(10) 进入block回调 然后加锁 然后 递归执行testMethod(value - 1); 无限加锁 需要把 [lock lock];加到testMethod回调block上面才OK

案例2.NSRecursiveLock 无法多线程递归

    self.recursiveLock = [NSRecursiveLock alloc];
    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];
                if (value > 0) {
                    NSLog(@"current value = %d",value);
                    testMethod(value - 1);
                }
                [self.recursiveLock unlock]; 
            };
            testMethod(10);
           
        });
    }
 运行打印结果
2021-08-19 21:33:31.132244+0800 003-NSLock分析[3356:3838068] current value = 10
2021-08-19 21:33:31.132393+0800 003-NSLock分析[3356:3838068] current value = 9
2021-08-19 21:33:31.132482+0800 003-NSLock分析[3356:3838068] current value = 8
2021-08-19 21:33:31.132581+0800 003-NSLock分析[3356:3838068] current value = 7
2021-08-19 21:33:31.132698+0800 003-NSLock分析[3356:3838068] current value = 6
2021-08-19 21:33:31.132809+0800 003-NSLock分析[3356:3838068] current value = 5
2021-08-19 21:33:31.132916+0800 003-NSLock分析[3356:3838068] current value = 4
2021-08-19 21:33:31.133034+0800 003-NSLock分析[3356:3838068] current value = 3
2021-08-19 21:33:31.133139+0800 003-NSLock分析[3356:3838068] current value = 2
2021-08-19 21:33:31.133236+0800 003-NSLock分析[3356:3838068] current value = 1
崩溃

案例3.@synchronized(self)

 
    for (int i = 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
              @synchronized(self){
                if (value > 0) {
                    NSLog(@"current value = %d",value);
                    testMethod(value - 1);
                }
              }
            };
            testMethod(10);
           
        });
    }
 运行打印结果正常

三.NSCondtion的分析

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

四.foundation源码关于锁的封装

swift-corelibs-foundation

1.关于NSLock底层

Xnip2021-08-19_22-15-56.jpg

NSLock 遵循NSLocking协议 在swiftfoundation源码中查看得到

Xnip2021-08-19_22-18-32.jpg

Xnip2021-08-19_22-25-32.jpg

2.关于NSRecursiveLock底层

Xnip2021-08-19_22-27-34.jpg

3.关于NSCondition底层

Xnip2021-08-19_22-28-15.jpg

五.NSConditionLock分析上

- (void)lg_testConditonLock{

    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [conditionLock lockWhenCondition:1];
        NSLog(@"线程 1");
        [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
       
        [conditionLock lockWhenCondition:2];
        sleep(0.1);
        NSLog(@"线程 2");
        [conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];
       NSLog(@"线程 3");
       [conditionLock unlock];
    });
}
运行打印结果
2021-08-19 23:00:57.538144+0800 004-NSCondition[4294:3890532] 线程 3
2021-08-19 23:00:57.538374+0800 004-NSCondition[4294:3890530] 线程 2
2021-08-19 23:00:57.538560+0800 004-NSCondition[4294:3890531] 线程 1

这里我们有一些问题

1.NSConditionLock VC NSCondition

2.参数2->是什么东西

3.lockWhenCondition->如何控制

4.unlockWithCondition 做了什么

下面我们添加系统断点 断点:-[NSConditionLock initWithCondition:]

查看汇编 断点所有带b相关的 按步骤打印如下

1.[NSConditionLock initWithCondition:] 1.封装了一个NSCondition value

2.[? init:2]

3.-[NSConditionLock init]

4.-[NSConditionLock zone]

5.[NSCondition allocWithZone]

6.[NSCondition init]

我们看一下源码流程: Xnip2021-08-19_23-33-27.jpg

六.NSConditionLock 分析下

[NSConditionLock lockWhenCondition:]

1.[NSDate distantFuture]

2.-[NSConditionLock lockWhenCondition: beforeDate:]

2.1-[NSCondition lock]

2.2.[WaitUntilDate ]

2.3.0x000000000000001->返回true 不在等待

2.4.[NSCondition unlock]

我们看一下源码流程:

Xnip2021-08-19_23-34-11.jpg

Xnip2021-08-19_23-34-17.jpg

[NSConditionLock unlockWithCondition:]

1.[NSCondition lock]

2.[NSCondition broadcast]

3.[NSCondition unlock]

我们看一下源码流程:

Xnip2021-08-19_23-34-49.jpg

NSConditionLock总结

• 线程 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 值,然后进⾏⼴

播,唤醒当前的线程。