美文网首页iOS Kit
第三十二节—iOS的锁(一)

第三十二节—iOS的锁(一)

作者: L_Ares | 来源:发表于2020-12-06 20:09 被阅读0次

    本文为L_Ares个人写作,以任何形式转载请表明原文出处。

    锁,在多线程的编程中,经常出场的人物,本文只是自己对锁的一些学习,如有错误的地方,烦请各位看官不吝赐教。

    一、锁的基本概念

    在学习iOS中的锁之前,必须要明白,锁到底是一个什么东西?只有知道了什么是锁,才有可能去学习如何用锁。

    1. 锁的概念

    1. 锁是一种同步的机制。
    2. 锁是一个对象。
    3. 锁是为了保证某一个资源在同一时间,不被多个“潜在调用者”持有,保证资源在同一时间不会因抢夺而出现错误。
    4. 锁是对资源的访问限制。

    如图 :

    图1.1.0.png

    2. 死锁

    锁的概念告知了锁的优势,而死锁就会告知锁的问题。

    一定要搞清楚,死锁和阻塞是两个概念,阻塞不叫死锁。

    2.1 什么是死锁

    1. 中文名死锁,英文名deadlock,又名死结。
    2. 当两个及两个以上的运算单元在等待对方停止运行,从而获取对方持有的系统资源,但又没有一方提前退出争夺的时候,就会产生死锁。

    如图 :

    图1.2.0.png

    如果线程1和线程2,谁都不先释放自己对已拥有的锁对象的持有权,那么就会陷入互相等待对方先松手的状态,这就是死锁。

    2.2 产生死锁的必要条件

    1. 互斥条件 :
    资源只能在同一时间分配给某一个运算单元,如果其他的运算单元也要请求同一资源,则只能等待持有资源的运算单元使用完毕。
    2. 持有和等待 :
    某运算单元已经持有一个或多个资源,又请求了其他被占用的资源,这个运算单元并不释放自己已有的资源,持有资源进行新资源的等待。
    3. 不可剥夺条件 :
    指运算单元已经获得了资源,在没有使用完成该资源的情况下,该资源不可以被剥夺,只能等待运算单元使用完毕后自己释放。
    4. 循环等待 :
    指一组集合中有很多运算单元,它们互相持有其他运算单元的资源。

    二、锁的分类

    iOS三大类锁 :

    1. 自旋锁 :
    自旋锁是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用(也就是while(true))。由于线程在这一过程中保持执行,因此是一种忙碌等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。
    就是自旋锁会忙碌等待
    2. 互斥锁 :
    是一种用于多线程编程中,防止多条线程同时对同一公共资源(比如全局变量)进行读写的机制。它通过将代码切片成一个一个的临界区域达成。临界区域指的是一块对公共资源进行访问的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。
    互斥锁不会出现忙碌等待,仅仅是线程阻塞。
    3. 读写锁 :
    读写锁是计算机程序的并发控制的一种同步机制。读操作可并发重入,写操作是互斥的。所以也称共享-互斥锁多读者-单写者锁

    其余的比如条件锁、递归锁、信号量,全都是它们三个的上层的封装和实现。

    在说锁的分类之前,先说iOS锁的性能,然后按照锁的性能从最优到最差进行介绍。

    这张图是引自伽蓝大神的博客,对于我这种野生的iOS开发者,他的博客可以说让我学习了很多以前从来没有意识到的知识点,非常推荐。

    图2.0.0.png

    这张图不代表多线程下iOS中的锁的实际性能,只代表单线程中锁的性能。

    三、自旋锁

    1. 概念

    1. 自旋锁是计算机科学用于多线程同步的一种锁。线程会反复检查锁变量是否可用。
    2. 由于线程会在反复检查锁变量是否可用的过程中,依然保持向下执行任务,所以自旋锁是一种忙碌等待
    3. 一旦获取了自旋锁,线程会一直保持自旋锁的存在,一直到显示的释放自旋锁。

    2. 使用场景

    自旋锁常用于线程只会阻塞很短的时间的场合。但是,不可以用于单核单线程CPU

    3. iOS中的自旋锁

    iOS中的自旋锁有 :

    1. atomic : 没错,就是我们给属性定义的时候,经常提及的原子性。
    2. OSSpinLock : 想使用它,就必须要能保证访问锁的线程必须全部处于同一个优先级

    4. 说明

    4.1 对于atomic :

    原子性 : 指事务的不可分割性。即,一个事务的所有操作要么不间断的全部进行完成,要么一个也不要执行。

    • 使用方式 :

    @property (atomic) NSArray *array;

    • 注意 :
    1. array属性的settergetter方法各自都是一个事务。
    2. 并且这两个事务都具有原子性
    3. 所以对settergetter方法操作的时候,是线程安全的。
    4. 但是,对于整个属性array来说,未必是线程完全安全的。

    4.2 对于OSSpinLock :

    • 使用方式 :

    1. 初始化 :
    (1.1) OS_SPINLOCK_INIT 是宏定义的0
    (1.2) 约定解锁为0,加锁为非0
    OSSpinLock lock = OS_SPINLOCK_INIT;
    2. 加锁 :
    OSSpinLockLock(&lock);
    3. 解锁:
    OSSpinLockUnlock(&lock);

    • 举例 :
    #import <libkern/OSAtomic.h>
    #pragma mark - 自旋锁
    static int number = 0;
    - (void)jd_spin_lock
    {
        //初始化OSSpinLock
        __block OSSpinLock jd_spin_lock = OS_SPINLOCK_INIT;
        //获取全局并发队列,并且队列的优先级设置为默认,保证OSSpinLock在同一优先级下工作
        dispatch_queue_t jd_global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        //并发执行任务1,调用公共资源
        dispatch_async(jd_global_queue, ^{
            OSSpinLockLock(&jd_spin_lock);
            number = 3;
            [self func_jd_lock_test_use];
            OSSpinLockUnlock(&jd_spin_lock);
        });
        //并发执行任务2,调用公共资源
        dispatch_async(jd_global_queue, ^{
            OSSpinLockLock(&jd_spin_lock);
            number = 4;
            [self func_jd_lock_test_use];
            OSSpinLockUnlock(&jd_spin_lock);
        });
    }
    #pragma mark - 锁测试用
    - (void)func_jd_lock_test_use
    {
        while (number > 0) {
            number--;
            [NSThread sleepForTimeInterval:1.f];
            NSLog(@"线程 : %@ --- num : %d",[NSThread currentThread],number);
        }
    }
    
    • 举例结果 :
    图3.1.0.png
    • 存在问题 :

    这里引用伽蓝大神的博客

    1. 当低优先级的线程先获得了锁并且访问资源的时候,高优先级线程也尝试获得这个锁。
    2. 于是高优先级线程就进入了忙碌等待,一边继续向下执行任务,一边等待低优先级交出这个锁的使用权。
    3. 但是高优先级是忙等状态,所以系统就执行高优先级的任务,这导致低优先级无法让系统帮忙完成任务,也就无法交出锁。于是就变成了互相等待。
    4. 所以,不建议使用OSSpinLock,如果遇到阻塞时间只需很短的时间的场合,非要使用的话,必须保证线程的优先级是一样的。

    四、信号量

    1. 概念

    信号量,有人也称之为信号灯,用来保证一段代码段不被多线程并发调用。信号量是使线程阻塞,而不是忙碌等待

    在之前的章节GCD(二)中,详细的说明了它的使用方法。

    GCD信号量是通过对信号量计数和0的对比来进行锁的实现。

    1. 当信号量的信号计数 >= 0,使信号计数-1,并不造成线程阻塞。
    2. 当信号量的信号计数 < 0,其所在的线程会被阻塞执行,直到信号计数>=0为止。

    2. 适用场景

    当一段关键代码可能会被多线程同时并发调用的时候,为了保证访问变量的正确性,可以使用信号量。

    3. iOS中的信号量

    iOS中最常使用的信号量就是GCD的

    • dispatch_semaphore_t

    4. 说明

    详情可见之前写过的GCD(二)——六、信号量。这里不再赘述。

    五、互斥锁

    1. 概念

    1. 首先,pthread_mutex中的p指的是POSIX
    2. pthread_mutex其实直译并没有锁的存在,直译叫多线程互斥量pthread_mutex可以叫互斥锁,也可以叫多线程互斥量,或者多线程互斥器
    3. 所谓互斥,即是同一时间,只有一条线程被允许访问临界资源。其他想要访问临界资源的线程会被阻塞而进入睡眠状态,而不是忙碌等待
    4. 互斥锁的实现原理和信号量非常的相似。需要进行上下文的切换。

    2. 适用场景

    (同信号量一样)当共享资源在同一时间内可能被多条线程操作,为了保证每条线程操作的正确性,可以使用互斥锁。

    3. iOS中的互斥锁

    1. pthread_mutex : 是iOS中,多种类型的锁的底层实现。
    2. NSLock : 继承于NSObject,通过实现NSLocking协议,拥有了lockunlock方法。内部的实现依然是依靠pthread_mutex。而其效率较pthread_mutex差一些的原因可能是因为它的互斥属性是PTHREAD_MUTEX_ERRORCHECK,会因为检查错误、提供错误提示,消耗一定的性能。
    3. @synchronized : 本质是互斥锁。需要使用一个OC对象来充当锁的角色,OC通过它,牺牲性能来换取书写和阅读上的高可读性。

    4. 说明

    4.1 对于pthread_mutex

    • 使用方法 :

    pthread_mutex本身是C语言的语法。需要引入头文件#import <pthread/pthread.h>

    1、使用pthread_mutex要先拥有互斥属性
    (1.1) 首先,定义互斥属性对象
    pthread_mutexattr_t pth_attr;
    (1.2) 其次,初始化互斥属性对象
    pthread_mutexattr_init(&pth_attr);
    (1.3) 最后,设置互斥属性对象
    pthread_mutexattr_settype(&pth_attr,Mutex_type_attributes);

    2、然后设置pthread_mutex互斥量
    (2.1) 静态初始化互斥量
    pthread_mutex_t pth_static_init_mutex = PTHREAD_MUTEX_INITIALIZER;
    (2.2) 动态初始化互斥量
    pthread_mutex_t pth_dynamic_init_mutex;
    pthread_mutex_init(&pth_dynamic_init_mutex, &pth_attr);
    (2.3) 互斥量加锁
    pthread_mutex_lock(&pth_dynamic_init_mutex);
    (2.4) 互斥量解锁
    pthread_mutex_unlock(&pth_dynamic_init_mutex);

    3、Mutex_type_attributes互斥量属性类型
    (3.1) #define PTHREAD_MUTEX_NORMAL 0 : 此类型互斥量不会自动检测死锁。
    (3.2) #define PTHREAD_MUTEX_ERRORCHECK 1 : 此类型互斥量会自动检测死锁。
    (3.3) #define PTHREAD_MUTEX_RECURSIVE 2 : 递归锁。进程必须是私有的(作用域属性PTHREAD_PROCESS_PRIVATE)才可以使用。
    (3.4) #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL : 默认就是3.1中的普通互斥量。

    4、进程作用域
    因为iOS是单进程,所以不需要考虑多进程的情况。
    (4.1) PTHREAD_PROCESS_PRIVATE : 作用域是进程内部。
    (4.2) PTHREAD_PROCESS_SHARED : 作用域是进程间。

    举例 :

    在下面的5. 递归锁——>5.4 说明——>5.4.2中进行了属性为递归锁的举例,其他互斥类型使用方法一致,请移步下面例子,这里不再重复举例。

    4.2 对于NSLock

    • 使用方法 :
    1. - (void)lock; : 加锁。
    2. - (void)unlock; : 解锁。
    3. - (BOOL)tryLock; :lock一样,加锁。不同的是tryLock可以在没有获得锁的时候,继续执行其他任务的处理。
    4. - (BOOL)lockBeforeDate:(NSDate *)limit; :limit时间点之前获得锁。
      • 如果获得成功,返回YES
      • 如果获得失败,返回NO
    • 举例 :
    #pragma mark - 互斥锁NSLock
    - (void)jd_mutex_lock
    {
        //获取全局并发队列
        dispatch_queue_t jd_global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //创建一个NSLock对象
        NSLock *jd_ns_lock = [[NSLock alloc] init];
        //并发执行任务1,调用公共资源
        dispatch_async(jd_global_queue, ^{
            [jd_ns_lock lock];
            number = 3;
            [self func_jd_lock_test_use];
            [jd_ns_lock unlock];
        });
        //并发执行任务2,调用公共资源
        dispatch_async(jd_global_queue, ^{
            [jd_ns_lock lock];
            number = 4;
            [self func_jd_lock_test_use];
            [jd_ns_lock unlock];
        });
    }
    #pragma mark - 锁测试用
    - (void)func_jd_lock_test_use
    {
        while (number > 0) {
            number--;
            [NSThread sleepForTimeInterval:1.f];
            NSLog(@"线程 : %@ --- num : %d",[NSThread currentThread],number);
        }
    }
    
    • 举例结果 :
      图2.3.0.png

    4.3 对于@synchronized

    • 使用方法 :
     @synchronized (Class obj) {
         要使用的资源
     }
    

    obj必须是OC对象类型。

    • 举例 :
    #pragma mark - 互斥锁@synchronized
    - (void)jd_synchronized
    {
        //获取全局并发队列
        dispatch_queue_t jd_global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //并发执行任务1,调用公共资源
        dispatch_async(jd_global_queue, ^{
            @synchronized (self) {
                number = 3;
                [self func_jd_lock_test_use];
            }
        });
        //并发执行任务2,调用公共资源
        dispatch_async(jd_global_queue, ^{
            @synchronized (self) {
                number = 4;
                [self func_jd_lock_test_use];
            }
        });
    }
    #pragma mark - 锁测试用
    - (void)func_jd_lock_test_use
    {
        while (number > 0) {
            number--;
            [NSThread sleepForTimeInterval:1.f];
            NSLog(@"线程 : %@ --- num : %d",[NSThread currentThread],number);
        }
    }
    
    • 举例结果 :
      图2.3.1.png

    六、条件变量

    1. 概念

    条件变量本质上并不是锁,它是线程间的通讯机制,是一种类生产模式的编码实现。当线程中的资源不满足使用条件的时候,线程被阻塞,进入休眠。当线程中的资源满足使用条件时,线程被唤醒,继续执行对应的任务。

    可简单的理解为 : 条件变量 = 互斥量 + 信号量

    2. 适用场景

    当存在类似生产者——消费者模型的情况下,即,某线程需要等待其他线程对资源进行改变,以达到约定条件,某线程才继续执行任务的场景下,可以使用条件变量。

    3. iOS中的条件变量

    • NSCondition : 底层是由pthread_cond_t实现的,它和信号量类似,却不完全相同,它提供了线程阻塞和信号机制。阻塞某线程,并且等待某个资源达到线程执行任务的条件时,唤醒线程,执行任务。
      • NSCondition携带NSLocking协议,因此拥有协议提供的lockunlock方法。和NSLock一样,可以令线程具有同步性,用法完全一致。可参考本文上面的NSLock的说明举例。
      • NSCondition本身则定义了类似与信号量的waitsignal方法,可以阻塞线程,用法也和GCD的信号量类似。

    4. 说明

    • 使用方法 :
    1. NSLocking协议带有 :
      (1.1) - (void)lock; : 加锁。
      (1.2) - (void)unlock; : 解锁。
    2. NSCondition自带 :
      (2.1) - (void)wait; : 释放互斥量,当前线程立即进入休眠,其他线程依然执行任务。
      (2.2) - (BOOL)waitUntilDate:(NSDate *)limit; : 释放互斥量,当前线程立即进入休眠,其他线程继续执行任务,直到limit时间点,当前线程再被唤醒。
      (2.3) - (void)signal; : 唤醒一条正在休眠的,并且满足条件变量的线程。
      (2.4) - (void)broadcast; : 唤醒所有正在休眠,并且满足条件变量的线程。
    • 举例 :
    @interface ViewController ()
    @property (nonatomic,strong) NSCondition *my_condition;
    @property (nonatomic,assign) NSInteger ticketsCount;
    @end
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self jd_lock_init];
        [self jd_condition];
    }
    #pragma mark - 锁的初始化
    - (void)jd_lock_init
    {
        //条件变量
        self.my_condition = [[NSCondition alloc] init];
    }
    #pragma mark - 条件变量
    - (void)jd_condition
    {
        //首先让票数归0
        self.ticketsCount = 0;
        //获取全局并发队列
        dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //循环开启多条生产者——消费者模型
        for (int i = 0; i < 10; i++) {
            //消费
            dispatch_async(global_queue, ^{
                [self jd_condition_customer];
            });
            //生产
            dispatch_async(global_queue, ^{
                [self jd_condition_producer];
            });
        }
    }
    //生产者
    - (void)jd_condition_producer
    {
        //加锁,保证生产者的线程安全
        [self.my_condition lock];
        //进行生产
        self.ticketsCount += 1;
        NSLog(@"线程 : %@ --- 生产了一张票 --- 现有票数:%ld",[NSThread currentThread],(unsigned long)self.ticketsCount);
        //发送信号,我生产了一张票
        [self.my_condition signal];
        //解锁,让其他线程可以调用生产者继续生产票
        [self.my_condition unlock];
    }
    //消费者
    - (void)jd_condition_customer
    {
        //消费者也要加锁,为了线程安全
        [self.my_condition lock];
        //等待生产者生产票
        while (self.ticketsCount == 0) {
            NSLog(@"没有票,线程休眠 : %@",[NSThread currentThread]);
            //释放互斥量,既然不干活,那就别持有资源不放,线程去休眠
            [self.my_condition wait];
        }
        //生产者生产出来多余的票可用
        self.ticketsCount -= 1;
        NSLog(@"线程 : %@ --- 消费了一张票 --- 现有票数:%ld",[NSThread currentThread],(unsigned long)self.ticketsCount);
        
        //消费完了就别占地方,让其他的线程可以消费,解锁
        [self.my_condition unlock];
    }
    
    • 举例结果 :
    图2.4.0.png

    七、递归锁

    1. 概念

    递归锁,又名可重入锁。递归锁是可以在递归加锁的情况下,递归内部依然可以使用资源,也就是可以对同一条线程多次加锁,不会造成阻塞。

    2. 适用场景

    当对公共资源的使用存在递归加锁的情况下,应该使用递归锁。

    3. iOS中的递归锁

    1. NSRecursiveLock :基于pthread_mutex_lock函数实现,对象类型是PTHREAD_MUTEX_RECURSIVE
    2. pthread_mutex_t : 需要引入头文件#import <pthread/pthread.h>
      ,pthread的递归锁。

    4. 说明

    4.1 对于NSRecursiveLock

    • 使用方法 :
    1. - (void)lock; : 加锁。
    2. - (void)unlock; : 解锁。
    3. - (BOOL)tryLock; :lock一样,加锁。不同的是tryLock可以在没有获得锁的时候,继续执行其他任务的处理。
    4. - (BOOL)lockBeforeDate:(NSDate *)limit; :limit时间点之前获得锁。
      • 如果获得成功,返回YES
      • 如果获得失败,返回NO
    • 举例 :

    使用普通锁的情况下 :

    #import "ViewController.h"
    @interface ViewController ()
    @property (nonatomic,strong) NSLock *normal_lock;
    @property (nonatomic,strong) NSRecursiveLock *recursive_lock;
    @end
    
    @implementation ViewController
    #pragma mark - 程序执行
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        
        [self jd_lock_init];
        
        [self jd_recursive_lock];
        
    }
    #pragma mark - 锁的初始化
    - (void)jd_lock_init
    {
        //普通互斥锁
        self.normal_lock = [[NSLock alloc] init];
        //递归锁
        self.recursive_lock = [[NSRecursiveLock alloc] init];
    }
    #pragma mark - 递归锁
    - (void)jd_recursive_lock
    {
        
        //线程x想要用func_recursive_lock_test_use资源
        [NSThread detachNewThreadSelector:@selector(func_recursive_lock_test_use:) toTarget:self withObject:@"4"];
        
        //线程y也想要用func_recursive_lock_test_use资源
        [NSThread detachNewThreadSelector:@selector(func_recursive_lock_test_use:) toTarget:self withObject:@"5"];
        
    }
    #pragma mark - 递归锁的公共资源
    - (void)func_recursive_lock_test_use:(NSString *)value
    {
        NSLog(@"进入递归");
        //普通互斥锁——加锁
        [self.normal_lock lock];
        if ([value intValue] > 0) {
            NSLog(@"线程 : %@ - 值 : %@",[NSThread currentThread],value);
            [self func_recursive_lock_test_use:[NSString stringWithFormat:@"%d",[value intValue] - 1]];
        }
        //普通互斥锁——解锁
        [self.normal_lock unlock];
    }
    @end
    

    普通锁结果 :

    图7.0.0.png

    线程number=8先抢到了锁对象self.normal_lock的使用权,但是递归进行了1次,在继续加锁的时候,发生了阻塞,无法达成想要的结果。

    使用递归锁的情况下 :

    执行2中的其他代码不变,仅改变最后一个方法,变成使用递归锁。
    - (void)func_recursive_lock_test_use:(NSString *)value
    {
        //递归锁——加锁
        [self.recursive_lock lock];
        if ([value intValue] > 0) {
            NSLog(@"线程 : %@ - 值 : %@",[NSThread currentThread],value);
            [self func_recursive_lock_test_use:[NSString stringWithFormat:@"%d",[value intValue] - 1]];
        }
        //递归锁——解锁
        [self.recursive_lock unlock];
    }
    

    递归锁结果 :

    图7.0.1.png

    使用递归锁可以有序的完成资源的使用。体现了加锁的同步性。

    4.2 对于pthread_mutexPTHREAD_MUTEX_RECURSIVE属性递归锁

    错误的例子就不重复列举,直接说它的用法。上面有对整个pthread_mutex的介绍,这里只说递归锁的使用。

    举例 :

    #import "ViewController.h"
    #import <pthread/pthread.h>
    @interface ViewController ()
    @property (nonatomic,strong) NSLock *normal_lock;
    @property (nonatomic,strong) NSRecursiveLock *recursive_lock;
    @end
    static pthread_mutex_t pth_lock;
    static pthread_mutexattr_t pth_attr;
    @implementation ViewController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        
        [self jd_lock_init];
        
        [self jd_recursive_lock];
        
    }
    - (void)jd_lock_init
    {
        //普通互斥锁
        self.normal_lock = [[NSLock alloc] init];
        //递归锁
        self.recursive_lock = [[NSRecursiveLock alloc] init];
        //pthread_lock
        pthread_mutexattr_init(&pth_attr);
        pthread_mutexattr_settype(&pth_attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&pth_lock, &pth_attr);
    }
    - (void)jd_recursive_lock
    {
        
        //线程x想要用func_recursive_lock_test_use资源
        [NSThread detachNewThreadSelector:@selector(func_recursive_lock_test_use:) toTarget:self withObject:@"4"];
        
        //线程y也想要用func_recursive_lock_test_use资源
        [NSThread detachNewThreadSelector:@selector(func_recursive_lock_test_use:) toTarget:self withObject:@"5"];
        
    }
    - (void)func_recursive_lock_test_use:(NSString *)value
    {
        //递归锁——加锁
        pthread_mutex_lock(&pth_lock);
        if ([value intValue] > 0) {
            NSLog(@"线程 : %@ - 值 : %@",[NSThread currentThread],value);
            [self func_recursive_lock_test_use:[NSString stringWithFormat:@"%d",[value intValue] - 1]];
        }
        //递归锁——解锁
        pthread_mutex_unlock(&pth_lock);
    }
    @end
    

    举例结果 :

    图2.3.3.png

    八、条件锁

    1. 概念

    所谓iOS中的条件锁,是指NSConditionLock,它是iOS针对NSCondition的更简单的实现,一样也是根据条件是否满足,来对线程进行休眠和唤醒的操作。

    2. 适用场景

    控制线程的执行顺序的时候可以使用。

    3. iOS中的条件锁

    • NSConditionLock : 它本身是借助NSCondition来实现阻塞线程和唤醒线程的,内部持有一个NSCondition对象和一个_condition_value属性。一样遵循NSLocking协议,所以也携带lockunLock方法。

    4. 说明

    • 使用方法 :

    1. 初始化方法 :
    - (instancetype)initWithCondition:(NSInteger)condition; : condition就是条件锁的初始条件,也就是上述的_condition_value
    2. NSLocking协议方法 :
    (2.1)- (void)lock; : 加锁
    (2.2)- (void)unlock; : 解锁
    3. NSConditionLock自身方法 :
    (3.1)- (void)lockWhenCondition:(NSInteger)condition; : 当条件为condition的时候,进行加锁操作。
    (3.2)- (BOOL)tryLock; :尝试获得锁,当没有获得到锁的时候,可以继续进行其他的操作。
    (3.3)- (BOOL)tryLockWhenCondition:(NSInteger)condition; :,当条件满足condition的时候,尝试获得锁,如果未能获得锁,则可以继续执行其他操作。
    (3.4)- (void)unlockWithCondition:(NSInteger)condition; : 解锁,并且将条件修改为condition

    • 举例 :
    #pragma mark - 条件锁
    - (void)jd_condition_lock
    {
        //获取全局并发队列
        dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(global_queue, ^{
            [self.my_condition_lock lockWhenCondition:0];
            NSLog(@"第一个执行,线程 : %@",[NSThread currentThread]);
            [self.my_condition_lock unlockWithCondition:1];
        });
        dispatch_async(global_queue, ^{
            [self.my_condition_lock lockWhenCondition:1];
            NSLog(@"第二个执行,线程 : %@",[NSThread currentThread]);
            [self.my_condition_lock unlockWithCondition:2];
        });
        dispatch_async(global_queue, ^{
            [self.my_condition_lock lockWhenCondition:2];
            NSLog(@"第三个执行,线程 : %@",[NSThread currentThread]);
            [self.my_condition_lock unlockWithCondition:3];
        });
        dispatch_async(global_queue, ^{
            [self.my_condition_lock lockWhenCondition:3];
            NSLog(@"第四个执行,线程 : %@",[NSThread currentThread]);
            [self.my_condition_lock unlockWithCondition:4];
        });
        dispatch_async(global_queue, ^{
            [self.my_condition_lock lockWhenCondition:4];
            NSLog(@"第五个执行,线程 : %@",[NSThread currentThread]);
            [self.my_condition_lock unlock];
        });
    }
    
    • 举例结果 :
    图2.6.0.png

    九、读写锁

    1. 概念

    读写锁是计算机程序的并发控制的一种同步机制,也称“共享—互斥锁”、“多读者—单写者锁”,读写锁可以在读的情况下并发重入,写的情况下则互斥。

    读写锁的三种状态 :

    1. 以读的方式占据锁的状态 :
      • 如果有其他的线程以读的方式请求占据锁,并读取锁内的共享资源,不会造成线程阻塞,允许其他线程进行读取,就像递归锁的可重入一样。
      • 如果有其他的线程以写的方式请求占据锁,企图更改锁内的共享资源,则会阻塞请求的线程,直到读的操作进行完毕。
      • 如果有其他多条线程,分别以读和写的不同方式请求占据锁,那么这些多条线程也会被阻塞,并且在当前线程读操作结束后,先让写方式的线程占据锁,避免读模式的锁长期占用资源,而写模式的锁却长期堵塞。
    2. 以写的方式占据锁的状态 : 所有其他请求占据锁的线程都会阻塞。
    3. 没有线程占据锁的状态 : 按照操作系统的调度顺序,依次调用,调度后要符合上述两种情况。

    2. 适用场景

    对于共享的资源,更多的是读取资源的情况下,可以使用读写锁。

    3. iOS中的读写锁

    • pthread_rwlock_t : 需要引入头文件#import <pthread/pthread.h>

    4. 说明

    • 使用方式 :

    1、使用pthread_rwlock要先拥有读写属性 :
    (1.1) 首先,定义读写属性对象
    pthread_rwlockattr_t pth_rw_attr;
    (1.2) 其次,初始化读写属性对象
    pthread_rwlockattr_init(&pth_rw_attr);
    (1.3) 最后,可以选择是否设置读写锁的共享标识
    pthread_rwlockattr_setpshared(&pth_rw_attr, PTHREAD_PROCESS_PRIVATE);

    • PTHREAD_PROCESS_PRIVATE : 表示该读写锁只能在其初始化所在线程所属的进程的线程中使用。说白了就是不能夸进程,对于iOS来说,这么设置就行了,因为iOS的App通常都是单进程。
    • PTHREAD_PROCESS_SHARED : 表示该读写锁是在多个进程共享的内存中分配,这些共享内存的进程中的线程都可以使用。也就是可以跨进程使用。

    2. 然后设置pthread_rwlock读写锁
    (2.1) 静态初始化读写锁 :
    pthread_rwlock_t pth_static_init_rwlock = PTHREAD_RWLOCK_INITIALIZER;
    (2.2) 动态初始化读写锁 :
    pthread_rwlock_t pth_dynamic_init_rwlock;
    pthread_rwlock_init(&pth_dynamic_init_rwlock, &pth_rw_attr);
    (2.3) 设置锁为读模式(可重入) :
    pthread_rwlock_rdlock(&pth_dynamic_init_rwlock);
    (2.4) 设置锁为写模式(不可重入) :
    pthread_rwlock_wrlock(&pth_dynamic_init_rwlock);
    (2.5) 解锁(统一的) :
    pthread_rwlock_unlock(&pth_dynamic_init_rwlock);

    • 举例 :
    #pragma mark - 读写锁
    static int pages = 10;
    - (void)jd_rwlock
    {
        dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(global_queue, ^{
            for (int i = 0; i < 10; i++) {
                dispatch_async(global_queue, ^{
                    [self jd_rwlock_read];
                });
            }
        });
        dispatch_async(global_queue, ^{
            for (int i = 0; i < 3; i++) {
                dispatch_async(global_queue, ^{
                    [self jd_rwlock_write];
                });
            }
        });
    }
    - (void)jd_rwlock_read
    {
        pthread_rwlock_rdlock(&pth_rw_lock);
        NSLog(@"线程 : %@ - 读资源 : %d",[NSThread currentThread],pages);
        pthread_rwlock_unlock(&pth_rw_lock);
    }
    - (void)jd_rwlock_write
    {
        pthread_rwlock_wrlock(&pth_rw_lock);
        [NSThread sleepForTimeInterval:2.f];
        pages++;
        NSLog(@"写资源 - 线程 : %@",[NSThread currentThread]);
        pthread_rwlock_unlock(&pth_rw_lock);
    }
    
    • 举例结果 :
    图2.7.0.png

    相关文章

      网友评论

        本文标题:第三十二节—iOS的锁(一)

        本文链接:https://www.haomeiwen.com/subject/jrrkwktx.html