本文为L_Ares个人写作,以任何形式转载请表明原文出处。
锁,在多线程的编程中,经常出场的人物,本文只是自己对锁的一些学习,如有错误的地方,烦请各位看官不吝赐教。
一、锁的基本概念
在学习iOS中的锁之前,必须要明白,锁到底是一个什么东西?只有知道了什么是锁,才有可能去学习如何用锁。
1. 锁的概念
- 锁是一种同步的机制。
- 锁是一个对象。
- 锁是为了保证某一个资源在同一时间,不被多个“潜在调用者”持有,保证资源在同一时间不会因抢夺而出现错误。
- 锁是对资源的访问限制。
如图 :
图1.1.0.png2. 死锁
锁的概念告知了锁的优势,而死锁就会告知锁的问题。
一定要搞清楚,死锁和阻塞是两个概念,阻塞不叫死锁。
2.1 什么是死锁
- 中文名死锁,英文名
deadlock
,又名死结。- 当两个及两个以上的运算单元在等待对方停止运行,从而获取对方持有的系统资源,但又没有一方提前退出争夺的时候,就会产生死锁。
如图 :
图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. 概念
- 自旋锁是计算机科学用于
多线程同步
的一种锁。线程会反复检查
锁变量是否可用。- 由于线程会在反复检查锁变量是否可用的过程中,依然保持向下执行任务,所以自旋锁是一种
忙碌等待
。- 一旦获取了自旋锁,线程会一直保持自旋锁的存在,一直到显示的释放自旋锁。
2. 使用场景
自旋锁常用于线程只会阻塞很短的时间的场合。但是,
不可以用于单核单线程CPU
。
3. iOS中的自旋锁
iOS中的自旋锁有 :
atomic
: 没错,就是我们给属性定义的时候,经常提及的原子性。OSSpinLock
: 想使用它,就必须要能保证访问锁的线程必须全部处于同一个优先级
。
4. 说明
4.1 对于atomic
:
原子性
: 指事务的不可分割性。即,一个事务的所有操作要么不间断的全部进行完成,要么一个也不要执行。
- 使用方式 :
@property (atomic) NSArray *array;
- 注意 :
array
属性的setter
和getter
方法各自都是一个事务。- 并且这两个事务都具有
原子性
。- 所以对
setter
和getter
方法操作的时候,是线程安全的。- 但是,对于整个属性
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);
}
}
- 举例结果 :
- 存在问题 :
这里引用伽蓝大神的博客。
- 当低优先级的线程先获得了锁并且访问资源的时候,高优先级线程也尝试获得这个锁。
- 于是高优先级线程就进入了忙碌等待,一边继续向下执行任务,一边等待低优先级交出这个锁的使用权。
- 但是高优先级是忙等状态,所以系统就执行高优先级的任务,这导致低优先级无法让系统帮忙完成任务,也就无法交出锁。于是就变成了互相等待。
- 所以,不建议使用
OSSpinLock
,如果遇到阻塞时间只需很短的时间的场合,非要使用的话,必须保证线程的优先级是一样的。
四、信号量
1. 概念
信号量,有人也称之为信号灯,用来保证一段代码段不被多线程并发调用。信号量是使线程阻塞,而
不是忙碌等待
。
在之前的章节GCD(二)中,详细的说明了它的使用方法。
GCD信号量是通过对信号量计数和0的对比来进行锁的实现。
- 当信号量的
信号计数 >= 0
,使信号计数-1,并不造成线程阻塞。- 当信号量的
信号计数 < 0
,其所在的线程会被阻塞执行,直到信号计数>=0
为止。
2. 适用场景
当一段关键代码可能会被多线程同时并发调用的时候,为了保证访问变量的正确性,可以使用信号量。
3. iOS中的信号量
iOS中最常使用的信号量就是GCD的
dispatch_semaphore_t
4. 说明
详情可见之前写过的GCD(二)——六、信号量。这里不再赘述。
五、互斥锁
1. 概念
- 首先,
pthread_mutex
中的p
指的是POSIX
。pthread_mutex
其实直译并没有锁的存在,直译叫多线程互斥量
。pthread_mutex
可以叫互斥锁
,也可以叫多线程互斥量
,或者多线程互斥器
。- 所谓互斥,即是同一时间,只有一条线程被允许访问临界资源。其他想要访问临界资源的线程会被阻塞而进入睡眠状态,而
不是忙碌等待
。- 互斥锁的实现原理和信号量非常的相似。需要进行上下文的切换。
2. 适用场景
(同信号量一样)当共享资源在同一时间内可能被多条线程操作,为了保证每条线程操作的正确性,可以使用互斥锁。
3. iOS中的互斥锁
pthread_mutex
: 是iOS中,多种类型的锁的底层实现。NSLock
: 继承于NSObject
,通过实现NSLocking
协议,拥有了lock
和unlock
方法。内部的实现依然是依靠pthread_mutex
。而其效率较pthread_mutex
差一些的原因可能是因为它的互斥属性是PTHREAD_MUTEX_ERRORCHECK
,会因为检查错误、提供错误提示,消耗一定的性能。@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
- 使用方法 :
- (void)lock;
: 加锁。- (void)unlock;
: 解锁。- (BOOL)tryLock;
: 和lock
一样,加锁。不同的是tryLock
可以在没有获得锁的时候,继续执行其他任务的处理。- (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协议
,因此拥有协议提供的lock
和unlock
方法。和NSLock
一样,可以令线程具有同步性,用法完全一致。可参考本文上面的NSLock
的说明举例。NSCondition
本身则定义了类似与信号量的wait
和signal
方法,可以阻塞线程,用法也和GCD的信号量类似。
4. 说明
- 使用方法 :
NSLocking协议
带有 :
(1.1)- (void)lock;
: 加锁。
(1.2)- (void)unlock;
: 解锁。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];
}
- 举例结果 :
七、递归锁
1. 概念
递归锁,又名可重入锁。递归锁是可以在递归加锁的情况下,递归内部依然可以使用资源,也就是可以对同一条线程多次加锁,不会造成阻塞。
2. 适用场景
当对公共资源的使用存在递归加锁的情况下,应该使用递归锁。
3. iOS中的递归锁
NSRecursiveLock
:基于pthread_mutex_lock
函数实现,对象类型是PTHREAD_MUTEX_RECURSIVE
。pthread_mutex_t
: 需要引入头文件#import <pthread/pthread.h>
,pthread的递归锁。
4. 说明
4.1 对于NSRecursiveLock
- 使用方法 :
- (void)lock;
: 加锁。- (void)unlock;
: 解锁。- (BOOL)tryLock;
: 和lock
一样,加锁。不同的是tryLock
可以在没有获得锁的时候,继续执行其他任务的处理。- (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_mutex
的PTHREAD_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协议
,所以也携带lock
和unLock
方法。
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];
});
}
- 举例结果 :
九、读写锁
1. 概念
读写锁是计算机程序的并发控制的一种同步机制,也称“共享—互斥锁”、“多读者—单写者锁”,读写锁可以在读的情况下并发重入,写的情况下则互斥。
读写锁的三种状态 :
以读的方式占据锁的状态
:
- 如果有其他的线程以读的方式请求占据锁,并读取锁内的共享资源,不会造成线程阻塞,允许其他线程进行读取,就像递归锁的可重入一样。
- 如果有其他的线程以写的方式请求占据锁,企图更改锁内的共享资源,则会阻塞请求的线程,直到读的操作进行完毕。
- 如果有其他多条线程,分别以读和写的不同方式请求占据锁,那么这些多条线程也会被阻塞,并且在当前线程读操作结束后,先让写方式的线程占据锁,避免读模式的锁长期占用资源,而写模式的锁却长期堵塞。
以写的方式占据锁的状态
: 所有其他请求占据锁的线程都会阻塞。没有线程占据锁的状态
: 按照操作系统的调度顺序,依次调用,调度后要符合上述两种情况。
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);
}
- 举例结果 :
网友评论