“这是我参与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数相关),但不能同时
既有读者⼜有写者。在读写锁保持期间也是抢占失效的。
如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,
直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须
⾃旋在那⾥,直到写者释放该读写锁。
⼀次只有⼀个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁. 正
是因为这个特性,
当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.
当读写锁在读加锁状态时, 所有试图以读模式对它进⾏加锁的线程都可以得到访问权, 但是如果
线程希望以写模式对此锁进⾏加锁, 它必须直到所有的线程释放锁.
通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞
随后的读模式锁请求, 这样可以避免读模式锁⻓期占⽤, ⽽等待的写模式锁请求⻓期阻塞.
读写锁适合于对数据结构的读次数⽐写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写
模式锁住时意味着独占, 所以读写锁⼜叫共享-独占锁.
二.NSLock和NSRecursiveLock的分析
案例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源码关于锁的封装
1.关于NSLock底层
NSLock 遵循NSLocking协议 在swift的foundation源码中查看得到
2.关于NSRecursiveLock底层
3.关于NSCondition底层
五.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.
NSConditionLockVCNSCondition2.参数2->是什么东西
3.
lockWhenCondition->如何控制4.
unlockWithCondition做了什么
下面我们添加系统断点 断点:-
[NSConditionLock initWithCondition:]查看汇编 断点所有带b相关的 按步骤打印如下
1.
[NSConditionLock initWithCondition:]1.封装了一个NSCondition value2.
[? init:2]3.
-[NSConditionLock init]4.
-[NSConditionLock zone]5.
[NSCondition allocWithZone]6.
[NSCondition init]
我们看一下源码流程:
六.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]
我们看一下源码流程:
[NSConditionLock unlockWithCondition:]1.
[NSCondition lock]2.
[NSCondition broadcast]3.
[NSCondition unlock]
我们看一下源码流程:
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 值,然后进⾏⼴
播,唤醒当前的线程。