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

本文为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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容