iOS 底层 day21 多线程锁 os_unfair_lock
2020-09-16 本文已影响0人
望穿秋水小作坊
一、 os_unfair_lock
1. os_unfair_lock 简介
- os_unfair_lock 用于取代不安全的 OSSpinLock,从 iOS10 开始支持
- 从底层调用(汇编)看,等待 os_unfair_lock 锁的线程会处于休眠状态,并非忙等
- 需要导入头文件
#import <os/lock.h>
2. os_unfair_lock 主要方法介绍
os_unfair_lock3. os_unfair_lock 代码演示
#import "OSUnFairLockDemo.h"
#import <os/lock.h>
@interface OSUnFairLockDemo ()
@property(nonatomic, assign) os_unfair_lock ticketLock;
@property(nonatomic, assign) os_unfair_lock moneyLock;
@end
@implementation OSUnFairLockDemo
- (instancetype)init
{
self = [super init];
if (self) {
self.ticketLock = OS_UNFAIR_LOCK_INIT;
self.moneyLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
- (void)__saleTicket {
os_unfair_lock_lock(&_ticketLock);
[super __saleTicket];
os_unfair_lock_unlock(&_ticketLock);
}
- (void)__saveMoney {
os_unfair_lock_lock(&_moneyLock);
[super __saveMoney];
os_unfair_lock_unlock(&_moneyLock);
}
- (void)__drawMoney {
os_unfair_lock_lock(&_moneyLock);
[super __drawMoney];
os_unfair_lock_unlock(&_moneyLock);
}
@end
-
YYBaseDemo
是封装了前面存取钱
和卖票
例子的代码,用于测试我们加的锁,封装代码我放在最后面。
二、 pthread_mutex
1. pthread_mutex 简介
-
mutex
叫做 “互斥锁”,等待锁的线程会处于休眠状态 - 需要导入头文件
#import <pthread.h>
-
pthread_
开头的一般是跨平台的
2. pthread_mutex 主要方法介绍
pthread_mutex3. pthread_mutex 代码演示
#import "PthreadMutexDemo.h"
#import <pthread.h>
@interface PthreadMutexDemo ()
@property(nonatomic, assign)pthread_mutex_t ticketMutex;
@property(nonatomic, assign)pthread_mutex_t monetMutex;
@end
@implementation PthreadMutexDemo
- (void) __initMutex:(pthread_mutex_t *)mutex{
// 初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
- (instancetype)init
{
self = [super init];
if (self) {
[self __initMutex:&_ticketMutex];
[self __initMutex:&_monetMutex];
}
return self;
}
- (void)__saleTicket {
pthread_mutex_lock(&_ticketMutex);
[super __saleTicket];
pthread_mutex_unlock(&_ticketMutex);
}
- (void)__saveMoney {
pthread_mutex_lock(&_monetMutex);
[super __saveMoney];
pthread_mutex_unlock(&_monetMutex);
}
- (void)__drawMoney {
pthread_mutex_lock(&_monetMutex);
[super __drawMoney];
pthread_mutex_unlock(&_monetMutex);
}
- (void)dealloc {
pthread_mutex_destroy(&_ticketMutex);
pthread_mutex_destroy(&_monetMutex);
}
@end
三、 pthread_mutex 递归锁
1. pthread_mutex 递归锁简介
- 递归锁:允许同一个线程对同一把锁重复加锁
2. os_unfair_lock 代码演示
- (void) __otherFunc {
pthread_mutex_lock(&_recursiveMutex);
static int invokeCount = 0;
if (invokeCount < 10) {
invokeCount++;
sleep(1.0);
NSLog(@"%d %@",invokeCount, [NSThread currentThread]);
[self __otherFunc];
}
pthread_mutex_unlock(&_recursiveMutex);
}
- 如果一个线程想执行上述代码,将会因为自我调用,重复加锁导致线程永远睡眠(死锁)
-
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
初始化时候,传递这个参数,即可获得一个递归锁
。这样就不会导致递归调用的死锁问题了。
四、 pthread_mutex 添加条件
1. pthread_mutex 添加条件简介
- 在多线程执行任务的是时,有时候我们需要保证
任务 1
先执行完毕,任务 2
才能开始执行,这时候我们就需要给锁添加条件
pthread_mutex 添加条件
2. 问题展示
- (void)__remove {
[self.mutableArray removeLastObject];
NSLog(@"__remove:%@",self.mutableArray);
}
- (void)__add {
[self.mutableArray addObject:@"11"];
NSLog(@"__add:%@",self.mutableArray);
}
- (void)otherTest {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self __remove];
});
dispatch_async(queue, ^{
[self __add];
});
}
- 我们需要保证无论哪个线程先调用,最终
mutableArray
数组都是空的
3. pthread_mutex 添加条件代码演示
#import "PthreadMutexDemo2.h"
#import <pthread.h>
@interface PthreadMutexDemo2 ()
@property(nonatomic, assign)pthread_mutex_t mutex;
@property(nonatomic, assign)pthread_cond_t cond;
@property(nonatomic, strong)NSMutableArray *mutableArray;
@end
@implementation PthreadMutexDemo2
- (instancetype)init
{
self = [super init];
if (self) {
// 初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&_mutex, &attr);
// 初始化锁的条件
pthread_cond_init(&_cond, NULL);
self.mutableArray = [NSMutableArray array];
}
return self;
}
- (void)__remove {
pthread_mutex_lock(&_mutex);
if (self.mutableArray.count == 0) {
pthread_cond_wait(&_cond, &_mutex);
}
[self.mutableArray removeLastObject];
NSLog(@"__remove:%@",self.mutableArray);
pthread_mutex_unlock(&_mutex);
}
- (void)__add {
pthread_mutex_lock(&_mutex);
[self.mutableArray addObject:@"11"];
NSLog(@"__add:%@",self.mutableArray);
// pthread_cond_broadcast(&_cond);
pthread_cond_signal(&_cond);
pthread_mutex_unlock(&_mutex);
}
- (void)otherTest {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self __remove];
});
dispatch_async(queue, ^{
sleep(1.0);
[self __add];
});
}
@end
- 这样就保证了无论何时都会先执行
__add
然后再执行__remove
五、 NSLock NSCondition
- NSLock 就是对 un_fair_lock 的 OC 包装
- NSCondition 就是对 pthread_mutex 的 OC 包装
- 用法类似,如果需要, todo:后面在添加用法展示
-todo:用断点追踪一下 NSLock 底层实现
六、 从汇编角度观察 OSSpinLock 、os_unfair_lock 和 pthread_mutex 等待锁的逻辑
1. OSSpinLock
- 打断点,断住第二个(第一个不需要等待锁)来到这句代码的子线程
OSSpinLockLock(&ticketsLock);
- 用
si
这个 lldb 指令,一直执行,会来到下图这个循环
OSSpinLock - 由汇编效果得知,
OSSpinLock
等待锁时,不会进入睡眠,会一直执行一个循环
2. os_unfair_lock
-
打断点,断住第二个(第一个不需要等待锁)来到这句代码的子线程
os_unfair_lock_lock(&_ticketLock);
-
用
os_unfair_locksi
这个 lldb 指令,一直执行,会来到下图
-
我们看到有个
syscall
,表示当前线程调用了系统的函数,再过掉此代码,发现断点消失了。 -
由此可知
os_unfair_lock
,会调用syscall
进入休眠状态。
3. pthread_mutex
-
打断点,断住第二个(第一个不需要等待锁)来到这句代码的子线程
pthread_mutex_lock(&_ticketMutex);
-
用
image.pngsi
这个 lldb 指令,一直执行,会来到下图
-
我们看到有个
syscall
,表示当前线程调用了系统的函数,再过掉此代码,发现断点消失了。 -
由此可知
pthread_mutex
等待锁的逻辑和os_unfair_lock
一样,会调用syscall
进入休眠状态。
七、 YYBaseDemo
是封装了前面存取钱
和卖票
例子的代码
#import "YYBaseDemo.h"
@interface YYBaseDemo ()
@property(nonatomic, assign) int moneyCount;
@property(nonatomic, assign) int ticketsCount;
@end
@implementation YYBaseDemo
- (void)moneyTest {
self.moneyCount = 1000;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[self __drawMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[self __saveMoney];
}
});
}
- (void)__drawMoney {
int readCount = self.moneyCount;
sleep(0.2); //模拟网络拥堵
int remainCount = readCount - 100;
self.moneyCount = remainCount;
NSLog(@"存钱---%@---剩余:%d",[NSThread currentThread], remainCount);
}
- (void)__saveMoney {
int readCount = self.moneyCount;
sleep(0.2); //模拟网络拥堵
int remainCount = readCount + 200;
self.moneyCount = remainCount;
NSLog(@"取钱---%@---剩余:%d",[NSThread currentThread], remainCount);
}
- (void)ticketTest {
self.ticketsCount = 20;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self __saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
}
- (void)__saleTicket {
int readCount = self.ticketsCount;
sleep(0.2); //模拟网络拥堵
int remainCount = readCount - 1;
self.ticketsCount = remainCount;
NSLog(@"卖票---%@---剩余:%d",[NSThread currentThread], remainCount);
}
- (void)otherTest{
}
@end