iOS 底层 day21 多线程锁 os_unfair_lock pthread_mutex NSLock NSCondition

一、 os_unfair_lock

1. os_unfair_lock 简介
  • os_unfair_lock 用于取代不安全的 OSSpinLock,从 iOS10 开始支持
  • 从底层调用(汇编)看,等待 os_unfair_lock 锁的线程会处于休眠状态,并非忙等
  • 需要导入头文件 #import
2. os_unfair_lock 主要方法介绍
os_unfair_lock
3. os_unfair_lock 代码演示
#import "OSUnFairLockDemo.h"
#import 

@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_ 开头的一般是跨平台的
2. pthread_mutex 主要方法介绍
pthread_mutex
3. pthread_mutex 代码演示
#import "PthreadMutexDemo.h"
#import 

@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. 思考如下递归代码,怎么才能同步调用?
- (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, ^{
        for (int i = 0 ; i < 10; i++) {
            [self __remove];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0 ; i < 10; i++) {
            [self __add];
        }
    });
}
  • 我们需要保证无论哪个线程先调用,最终 mutableArray数组都是空的
3. pthread_mutex 添加条件代码演示
#import "PthreadMutexDemo2.h"
#import 

@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 包装

六、 从汇编角度观察 OSSpinLock 、os_unfair_lock 和 pthread_mutex 等待锁的逻辑

1. OSSpinLock
  • 打断点,断住第二个(第一个不需要等待锁)来到这句代码的子线程 OSSpinLockLock(&ticketsLock);
  • si 这个 lldb 指令,一直执行,会来到下图这个循环
    OSSpinLock
  • 由汇编效果得知,OSSpinLock 等待锁时,不会进入睡眠,会一直执行一个循环
2. os_unfair_lock
  • 打断点,断住第二个(第一个不需要等待锁)来到这句代码的子线程 os_unfair_lock_lock(&_ticketLock);

  • si 这个 lldb 指令,一直执行,会来到下图

    os_unfair_lock

  • 我们看到有个 syscall ,表示当前线程调用了系统的函数,再过掉此代码,发现断点消失了。

  • 由此可知 os_unfair_lock ,会调用 syscall 进入休眠状态。

3. pthread_mutex
  • 打断点,断住第二个(第一个不需要等待锁)来到这句代码的子线程 pthread_mutex_lock(&_ticketMutex);

  • si 这个 lldb 指令,一直执行,会来到下图

    image.png

  • 我们看到有个 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

你可能感兴趣的:(iOS 底层 day21 多线程锁 os_unfair_lock pthread_mutex NSLock NSCondition)