对于iOS中各类锁的学习总结,供往后查阅,如有任何问题,请各位大佬帮忙指正html
平常开发中,@property (nonatomic, strong) *foo
是咱们不厌其烦的使用频率最高的声明方式,也很清楚atomic
和nonatomic
属性的区别,这里再复习一下这两个关键字:ios
atomic
:原子性,这个属性是默认的,经过在setter
、getter
中加锁保证数据的读写安全nonatomic
:非原子性,就是不加锁。优势是速度优于使用atomic
,大多数场景不会出现问题做为编译器标识符,@property
的做用是帮助咱们快速生成成员变量及其getter/setter方法,并经过属性关键字,帮助咱们管理内存及安全等繁杂的事务,那么atomic
是如何帮助咱们保证成员变量的读写安全呢?下面咱们看一段代码:objective-c
//@property(retain) UITextField *userName;
// 示例代码以下:
- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
_userName = retval;
}
return retval;
}
- (void) setUserName:(UITextField *)userName {
@synchronized(self) {
[_userName release];
_userName = [userName retain];
}
}
复制代码
咱们能够很容易的看出,编译器是经过加锁,来保证当前成员变量_userName
的读写安全,不至于生成脏数据,这即是atomic
背后,编译器帮咱们作的事情。事实上,若是深究下去编译器帮咱们加了什么锁,其实并不是@synchronized(object)
编程
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active;不会使线程进入阻塞状态(休眠),减小了没必要要的上下文切换,执行速度快缓存
非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候须要从内核态恢复,须要线程上下文切换,影响锁的性能安全
为何atomic
会作为默认属性,咱们不难看出,苹果这么设计是想告诉咱们,不少状况下,效率换安全是值得的bash
下面一段简单代码,考虑一下输出结果网络
- (void)unlockTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[string appendString:@"-Locked"];
NSLog(@"%@",string);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[string appendString:@"-JailBreaked"];
NSLog(@"%@",string);
});
}
复制代码
书写这样一段代码,是想在不一样线程中在改变变量后,使用这个变量session
控制台输出:多线程
2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225442] Mike-Locked-JailBreaked
2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225441] Mike-Locked-JailBreaked
复制代码
这显然不是想要的结果,如何保证咱们在不一样线程中使用的变量,都是咱们但愿的值呢?答案之一,就是加锁
OC为咱们提供了四种遵循的类,分别是NSLock
/NSCondtionLock
/NSRecursiveLock
/NSCondition
,知足面向对象编程的需求
@protocol NSLocking
- (void)lock;// 阻塞线程,线程休眠
- (void)unlock;
@end
复制代码
加锁的基本流程: 【加锁】->【操做】->【解锁】 以上提到的4个类,都可以实现这个基础功能,下文中再也不赘述
- (void)lockedTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// [锁 lock];
[string appendString:@"-Locked"];
NSLog(@"%@",string);
// [锁 unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [锁 lock];
[string appendString:@"-JailBreaked"];
NSLog(@"%@",string);
// [锁 unlock];
});
}
复制代码
控制台输出:
DiscoverLock_iOS[90562:11303793] Mike-Locked
DiscoverLock_iOS[90562:11303799] Mike-Locked-JailBreaked
复制代码
DiscoverLock_iOS[90562:11303793] Mike-JailBreaked
DiscoverLock_iOS[90562:11303799] Mike-JailBreaked-Locked
复制代码
这里的输出,结果不太同样,侧面说明了DISPATCH_QUEUE_PRIORITY
并不能保证线程的执行顺序,若是要明确执行顺序,属于线程同步的范畴,本文不展开讨论,只会在NSConditionLock部分简单示例如何使用该类作到同步
- (BOOL)tryLock;
:尝试加锁,若是失败返回NO,不会阻塞线程- (BOOL)lockBeforeDate:(NSDate *)limit;
:指定时间前尝试加锁,若是失败返回NO,到时间前阻塞线程示例代码:
- (void)lockTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
LOCK(
[string appendString:@"-Locked"];
NSLog(@"%@",string);
sleep(5);
)
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
TRYLOCK(
[string appendString:@"-UnLock"];
NSLog(@"%@",string);
sleep(3);
)
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
TRYLOCKINDURATION(2,
[string appendString:@"-Ending"];
NSLog(@"%@",string);
);
NSLog(@"-=-=-=-=-");
});
}
复制代码
控制台输出:
2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465678] Mike-Locked
2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465679] TryLock-NO
2019-11-11 19:54:08.807889+0800 DiscoverLock_iOS[92986:11465679] Mike-Locked-UnLock
2019-11-11 19:54:10.810165+0800 DiscoverLock_iOS[92986:11465677] TryLockBefore-NO
2019-11-11 19:54:10.810523+0800 DiscoverLock_iOS[92986:11465677] Mike-Locked-UnLock-Ending
2019-11-11 19:54:10.810810+0800 DiscoverLock_iOS[92986:11465677] -=-=-=-=-
复制代码
经过上面示例代码输出能够看到,- (BOOL)tryLock;
并不会阻塞线程,在尝试加锁失败时,当即返回了NO,可是- (BOOL)lockBeforeDate:(NSDate *)limit;
则在时间到以前阻塞了线程操做,在等待相应时间后,返回了NO,并执行了下一句打印,很明显是在等待期间阻塞了线程
上面代码中用到的几个宏定义,建议之后使用锁时,尽可能保持头脑清醒或者干脆定义一些便利方法,保证【上锁】-【解锁】的成对出现,避免线程阻塞或死锁的状况
#define LOCK(...) \
[_lock lock]; \
__VA_ARGS__; \
[_lock unlock]; \
#define TRYLOCK(...) \
BOOL locked = [_lock tryLock]; \
NSLog(@"%@",locked?@"TryLock-YES":@"TryLock-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \
#define TRYLOCKINDURATION(duration,...) \
BOOL locked = [_lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:duration]]; \
NSLog(@"%@",locked?@"TryLockBefore-YES":@"TryLockBefore-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \
复制代码
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
:便利构造方法,传入条件锁的初始值@property (readonly) NSInteger condition;
:当前条件锁的值- (void)lockWhenCondition:(NSInteger)condition;
:当锁的条件值与传入值相等时,执行接下来的操做,不然阻塞线程- (BOOL)tryLock;
:尝试加锁,若是失败返回NO,不会阻塞线程- (BOOL)tryLockWhenCondition:(NSInteger)condition;
:尝试加锁,当锁的条件值与传入值相等,则加锁成功,不然失败返回NO,不会阻塞线程- (void)unlockWithCondition:(NSInteger)condition;
:解锁操做,同时变动锁的条件值为传入值- (BOOL)lockBeforeDate:(NSDate *)limit;
:指定时间前尝试加锁,若是失败返回NO,到时间前阻塞线程- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
:指定时间前尝试加锁,当锁的条件值与传入值相等,则加锁成功返回YES,不然失败返回NO,到时间前阻塞线程NSConditionLock
和NSLock
方法相似,多了一个condition
属性,以及每一个操做都多了一个关于condition属性的方法,- (void)lockWhenCondition:(NSInteger)condition;
只有condition参数与初始化时候的condition相等,lock才能正确进行加锁操做。而- (void)unlockWithCondition:(NSInteger)condition;
并非当条件值符合条件时才解锁,而是解锁以后,修改当前锁的条件值 假如不使用condition相关的方法,NSConditionLock
同NSLock
并没有二致
上文中咱们提到了线程同步问题,这里一块儿看一下下面这段代码
- (void)conditionLockUnordered {
NSMutableString *conditionString = [[NSMutableString alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-1-"];
NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-2-"];
NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-3-"];
NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-4-"];
NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-5-"];
NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
}
复制代码
控制台输出:
2019-11-11 20:34:16.875479+0800 DiscoverLock_iOS[93895:11551560] >>> 2 -1--2--4--3- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<
2019-11-11 20:34:16.875525+0800 DiscoverLock_iOS[93895:11551562] >>> 3 -1--2--4--3- threadInfo:<NSThread: 0x600003903680>{number = 6, name = (null)}<<<
2019-11-11 20:34:16.875530+0800 DiscoverLock_iOS[93895:11551561] >>> 1 -1--2- threadInfo:<NSThread: 0x600003908bc0>{number = 3, name = (null)}<<<
2019-11-11 20:34:16.875543+0800 DiscoverLock_iOS[93895:11551559] >>> 4 -1--2--4--3- threadInfo:<NSThread: 0x6000039175c0>{number = 5, name = (null)}<<<
2019-11-11 20:34:16.875628+0800 DiscoverLock_iOS[93895:11551560] >>> 5 -1--2--4--3--5- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<
复制代码
依然是混乱状态,上文中NSLock
部分已经经过加锁,控制了读写的稳定性,那么若是咱们想要按照标号依次执行,该如何操做?
熟悉GCD
的小伙伴会说这还不简单,dispatch_barrier
解千愁,固然这么写没问题,可是这里多说一嘴,dispatch_barrier
只能针对同一个并发队列起做用,注意正确初始化的姿式dispatch_queue_t thread = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);
,而不是干啥都是一句dispatch_get_global_queue(0,0)
,若是使用Global_Queue,这个barrier就同普通的dispatch_async
没什么区别了
咱们要是想在不一样线程搞定顺序这个事儿,怎么办呢?这个时候NSConditionLock
自带的条件方法,便能帮你实现这个功能,具体看下面的示例代码
- (void)conditionLockOrdered {
// NSConditionLock
NSInteger conditionTag = 0;
_conditionLock = [[NSConditionLock alloc] initWithCondition:conditionTag];
NSMutableString *conditionString = [[NSMutableString alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 1 <<<");
[_conditionLock lockWhenCondition:conditionTag];
[conditionString appendString:@"-1-"];
NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 2 <<<");
[_conditionLock lockWhenCondition:1];
[conditionString appendString:@"-2-"];
NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:2];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 3 <<<");
[_conditionLock lockWhenCondition:2];
[conditionString appendString:@"-3-"];
NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:3];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@">>> handle 4 <<<");
[_conditionLock lockWhenCondition:3];
[conditionString appendString:@"-4-"];
NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:4];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@">>> handle 5 <<<");
[_conditionLock lockWhenCondition:4];
[conditionString appendString:@"-5-"];
NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlock];
NSLog(@"-=-=-=-=-=-=-");
});
NSLog(@"🍺");
}
复制代码
控制台输出:
2019-11-11 20:53:58.237847+0800 DiscoverLock_iOS[94374:11586439] 🍺
2019-11-11 20:53:58.237862+0800 DiscoverLock_iOS[94374:11586488] >>> handle 1 <<<
2019-11-11 20:53:58.237877+0800 DiscoverLock_iOS[94374:11586489] >>> handle 3 <<<
2019-11-11 20:53:58.237868+0800 DiscoverLock_iOS[94374:11586490] >>> handle 2 <<<
2019-11-11 20:53:58.237887+0800 DiscoverLock_iOS[94374:11586491] >>> handle 4 <<<
2019-11-11 20:53:58.237892+0800 DiscoverLock_iOS[94374:11586495] >>> handle 5 <<<
2019-11-11 20:53:58.238111+0800 DiscoverLock_iOS[94374:11586488] >>> 1 -1- threadInfo:<NSThread: 0x6000014c3380>{number = 3, name = (null)}<<<
2019-11-11 20:53:58.238488+0800 DiscoverLock_iOS[94374:11586490] >>> 2 -1--2- threadInfo:<NSThread: 0x6000014dac40>{number = 4, name = (null)}<<<
2019-11-11 20:53:58.238605+0800 DiscoverLock_iOS[94374:11586489] >>> 3 -1--2--3- threadInfo:<NSThread: 0x6000014daf00>{number = 5, name = (null)}<<<
2019-11-11 20:53:58.239269+0800 DiscoverLock_iOS[94374:11586491] >>> 4 -1--2--3--4- threadInfo:<NSThread: 0x6000014c6740>{number = 6, name = (null)}<<<
2019-11-11 20:53:58.239410+0800 DiscoverLock_iOS[94374:11586495] >>> 5 -1--2--3--4--5- threadInfo:<NSThread: 0x6000014c3480>{number = 7, name = (null)}<<<
2019-11-11 20:53:58.239552+0800 DiscoverLock_iOS[94374:11586495] -=-=-=-=-=-=-
复制代码
能够看到,不一样的线程,虽然被调度的时机不一样,可是由于NSConditionLock
的存在,后续对数据具体的操做,咱们预想的顺序获得了保证。这种用法笔者并认为在任务耗时较少的状况下没有明显问题的,可是假如存在长时间的耗时操做,仍是建议使用dispatch_barrier
,由于这样不会占用过多资源
- (BOOL)tryLock;
:尝试加锁,若是失败返回NO,不会阻塞线程- (BOOL)lockBeforeDate:(NSDate *)limit;
:指定时间前尝试加锁,若是失败返回NO,到时间前阻塞线程 Api同NSLock
彻底同样,区别在于NSRecursiveLock(递归锁)
能够在同一线程中重复加锁而不死锁,它会记录【上锁】和【解锁】的次数,当这两个值平衡时,才会释放锁,其余线程才能够上锁成功先看下一段代码,会存在什么问题:
@property (nonatomic, assign) NSInteger recursiveNum;// 5
- (void)test_unrecursiveLock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursiveTest];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.recursiveNum = 7;
NSLog(@">>> changed %ld <<<",self.recursiveNum);
});
}
- (void)recursiveTest {
if (self.recursiveNum > 0) {
self.recursiveNum -= 1;
NSLog(@">>> %ld <<<",self.recursiveNum);
[self recursiveTest];
}
}
复制代码
控制台输出:
2019-11-11 21:27:13.451703+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<<
2019-11-11 21:27:13.451709+0800 DiscoverLock_iOS[95105:11645277] >>> changed 7 <<<
2019-11-11 21:27:13.451812+0800 DiscoverLock_iOS[95105:11645279] >>> 6 <<<
2019-11-11 21:27:13.451883+0800 DiscoverLock_iOS[95105:11645279] >>> 5 <<<
2019-11-11 21:27:13.451940+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<<
2019-11-11 21:27:13.452004+0800 DiscoverLock_iOS[95105:11645279] >>> 3 <<<
2019-11-11 21:27:13.452068+0800 DiscoverLock_iOS[95105:11645279] >>> 2 <<<
2019-11-11 21:27:13.452130+0800 DiscoverLock_iOS[95105:11645279] >>> 1 <<<
2019-11-11 21:27:13.452241+0800 DiscoverLock_iOS[95105:11645279] >>> 0 <<<
复制代码
同时存在两个线程,对已知的recursiveNum的值进行写操做,其中一个线程使用递归调用,对该值进行了操做,可是同时另外一个线程改变了这个值,在不加锁的状况下,这种操做问题不少,若是递归中含有重要的逻辑处理,竞态可能致使整个逻辑执行完的结果大几率是错误的。
如何规避这种竞态致使的没必要要的错误,首先咱们想到的是加锁,可是若是递归加锁的话,线程会重复加锁,致使死锁。因此这时候必须使用递归锁来解决这个问题
- (void)test_unrecursiveLock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursiveTest];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[_recursiveLock lock];// 递归锁
self.recursiveNum = 7;
NSLog(@">>> changed %ld <<<",self.recursiveNum);
[_recursiveLock unlock];// 解锁
});
}
- (void)recursiveTest {
[_recursiveLock lock];// 递归锁
if (self.recursiveNum > 0) {
self.recursiveNum -= 1;
NSLog(@">>> %ld <<<",self.recursiveNum);
[self recursiveTest];
}
[_recursiveLock unlock];// 解锁
}
复制代码
控制台输出:
2019-11-11 21:34:44.422337+0800 DiscoverLock_iOS[95341:11655990] >>> 4 <<<
2019-11-11 21:34:44.422442+0800 DiscoverLock_iOS[95341:11655990] >>> 3 <<<
2019-11-11 21:34:44.422511+0800 DiscoverLock_iOS[95341:11655990] >>> 2 <<<
2019-11-11 21:34:44.422583+0800 DiscoverLock_iOS[95341:11655990] >>> 1 <<<
2019-11-11 21:34:44.422645+0800 DiscoverLock_iOS[95341:11655990] >>> 0 <<<
2019-11-11 21:34:44.422747+0800 DiscoverLock_iOS[95341:11655992] >>> changed 7 <<<
------
2019-11-11 21:37:11.238448+0800 DiscoverLock_iOS[95396:11662426] >>> changed 7 <<<
2019-11-11 21:37:11.238635+0800 DiscoverLock_iOS[95396:11662423] >>> 6 <<<
2019-11-11 21:37:11.238793+0800 DiscoverLock_iOS[95396:11662423] >>> 5 <<<
2019-11-11 21:37:11.238930+0800 DiscoverLock_iOS[95396:11662423] >>> 4 <<<
2019-11-11 21:37:11.239093+0800 DiscoverLock_iOS[95396:11662423] >>> 3 <<<
2019-11-11 21:37:11.239293+0800 DiscoverLock_iOS[95396:11662423] >>> 2 <<<
2019-11-11 21:37:11.239844+0800 DiscoverLock_iOS[95396:11662423] >>> 1 <<<
2019-11-11 21:37:11.239976+0800 DiscoverLock_iOS[95396:11662423] >>> 0 <<<
复制代码
虽然存在两种输出结果,可是咱们的递归操做的逻辑,能够彻底不受干扰,若是须要控制顺序,(敲黑板)要怎么作呢?
- (void)wait;
:当前线程当即进入休眠状态- (BOOL)waitUntilDate:(NSDate *)limit;
:当前线程当即进入休眠状态,limit时间后唤醒- (void)signal;
:唤醒wait后进入休眠的单条线程- (void)broadcast;
:唤醒wait后进入休眠的全部线程,调度有些状况须要协调线程之间的执行。例如,一个线程可能须要等待其余线程返回结果,这个时候NSCondition
多是个好选择
为了能体现NSCondition的做用,咱们举一个可能并非很恰当的生产者-消费者的例子: 咱们如今有一条柔性生产线,限定每一个批次只能生产3件商品,耗时6s,同时开放网络购买平台让你们抢购拼团,订单式销售,三人成团,如今有三位天选之子Tom/Mike/Lily从全球千万人中脱颖而出,成功成团。为了加强可玩性,活动是从开启的一刻起,同时开始生产和抢购,3件库存销售完成后,再次进行同时进行生产和抢购活动
代码示例以下:
@interface Producer : NSObject
@property (nonatomic, assign) BOOL shouldProduce;
@property (nonatomic, strong) NSString *itemName;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector;
- (void)produce;
@end
@implementation Producer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector{
self = [super init];
if (self) {
self.condition = condition;
self.collector = collector;
self.shouldProduce = NO;
self.itemName = nil;
}
return self;
}
-(void)produce{
self.shouldProduce = YES;
while (self.shouldProduce) {
NSLog(@"准备生产");
[self.condition lock];
NSLog(@"- p lock -");
if (self.collector.count > 0 ) {
NSLog(@"- p - wait");
[self.condition wait];
}
NSLog(@"开始生产");
[self.collector addObject:@"商品1"];
[self.collector addObject:@"商品2"];
[self.collector addObject:@"商品3"];
NSLog(@"生产:商品1/商品2/商品3");
sleep(6);
NSLog(@"生产结束");
[self.condition broadcast];
NSLog(@"- p signal -");
[self.condition unlock];
NSLog(@"- p unlock -");
}
NSLog(@"-结束生产-");
}
@end
@interface Consumer : NSObject
@property (nonatomic, assign) BOOL shouldConsumer;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
@property (nonatomic, copy) NSString *itemName;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name;
- (void)consumer;
@end
@implementation Consumer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name{
self = [super init];
if (self) {
self.condition = condition;
self.collector = collector;
self.shouldConsumer = NO;
self.itemName = name;
}
return self;
}
-(void)consumer{
self.shouldConsumer = YES;
while (self.shouldConsumer) {
NSLog(@"%@-准备购买",self.itemName);
[self.condition lock];
NSLog(@"- c:%@ lock -",self.itemName);
if (self.collector.count == 0 ) {
NSLog(@"- c:%@ wait -",self.itemName);
[self.condition wait];
}
NSString *item = [self.collector objectAtIndex:0];
NSLog(@"%@-买入:%@",self.itemName,item);
[self.collector removeObjectAtIndex:0];
sleep(2);
[self.condition signal];
NSLog(@"- c:%@ signal -",self.itemName);
[self.condition unlock];
NSLog(@"- c:%@ unlock -",self.itemName);
}
NSLog(@"-%@结束购买-",self.itemName);
}
@end
// 调用
{
NSMutableArray *pipeline = [NSMutableArray array];
NSCondition *condition = [NSCondition new];
Producer *p = [[Producer alloc] initWithConditon:condition collector:pipeline];
Consumer *c = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Tom"];
Consumer *c1 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Mike"];
Consumer *c2 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Lily"];
[[[NSThread alloc] initWithTarget:c selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:c1 selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:c2 selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:p selector:@selector(produce) object:p] start];
sleep(15);
NSLog(@"<----------------->");
p.shouldProduce = NO;
c.shouldConsumer = NO;
c1.shouldConsumer = NO;
c2.shouldConsumer = NO;
}
复制代码
部分控制台输出:
2019-11-12 17:04:03.662926+0800 DiscoverLock_iOS[7110:12246052] Mike-准备购买
2019-11-12 17:04:03.662916+0800 DiscoverLock_iOS[7110:12246051] Tom-准备购买
2019-11-12 17:04:03.662990+0800 DiscoverLock_iOS[7110:12246053] Lily-准备购买
2019-11-12 17:04:03.663005+0800 DiscoverLock_iOS[7110:12246054] 准备生产
2019-11-12 17:04:03.663083+0800 DiscoverLock_iOS[7110:12246053] - c:Lily lock -
2019-11-12 17:04:03.663144+0800 DiscoverLock_iOS[7110:12246053] - c:Lily wait -
2019-11-12 17:04:03.663254+0800 DiscoverLock_iOS[7110:12246052] - c:Mike lock -
2019-11-12 17:04:03.663439+0800 DiscoverLock_iOS[7110:12246052] - c:Mike wait -
2019-11-12 17:04:03.663805+0800 DiscoverLock_iOS[7110:12246051] - c:Tom lock -
2019-11-12 17:04:03.663903+0800 DiscoverLock_iOS[7110:12246051] - c:Tom wait -
2019-11-12 17:04:03.664126+0800 DiscoverLock_iOS[7110:12246054] - p lock -
2019-11-12 17:04:03.664297+0800 DiscoverLock_iOS[7110:12246054] 开始生产
2019-11-12 17:04:03.664433+0800 DiscoverLock_iOS[7110:12246054] 生产:商品1/商品2/商品3
2019-11-12 17:04:09.669735+0800 DiscoverLock_iOS[7110:12246054] 生产结束
复制代码
基于多线程并发的工做原理,经过上面的部分打印结果,也很容易获得这个结论。因为不符合购买条件,Lily/Mike/Tom都只能选择wait
,这个时候,生产者获取到锁并执行生产代码,在生产完成后,broadcast
或者signal
告诉其余线程,能够唤醒线程并继续执行消费者相关代码。 NSCondition
相较于NSConditionLock
的不一样点在于他依赖的是外部值,可以知足更多复杂需求场景。 假如将上述代码中生产者的broadcast
替换成signal
后发现,在当前这种特定场景下,这两个方法的做用彷佛并无什么区别。并且感兴趣的同窗,可使用上述代码多运行几回,看看是否可以得出同笔者同样的猜想:
看了NSCondition
这么个复杂的东西,咱们看点轻松的,OSSpinLock
是苹果在iOS10以前提供的自旋锁方案,可是存在优先级反转的问题,被苹果废弃,之前源码中使用OSSpinLock
的地方,都被苹果替换成了pthread_mutex
os_unfair_lock
是iOS10之后新增的低级别加锁方案,本质是互斥锁,这里须要注意,目前不少文章认为他是做为替代OSSpinLock
的方案就是自旋锁是有问题的
void os_unfair_lock_lock(os_unfair_lock_t lock);
:加锁bool os_unfair_lock_trylock(os_unfair_lock_t lock);
:尝试加锁,成功返回true,失败返回falsevoid os_unfair_lock_unlock(os_unfair_lock_t lock);
:解锁void os_unfair_lock_assert_owner(os_unfair_lock_t lock);
:若是当前线程未持有指定的锁,则触发断言void os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);
:若是当前线程持有指定的锁,则触发断言各方法同常见的锁没太大差异,能够看下方法注释,只是须要注意一下初始化方式
{
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
}
复制代码
@synchronized(object)
指令使用传入的对象做为该锁的惟一标识,只有当标识相同时,才知足互斥 @synchronized(object)
指令实现锁的优势就是咱们不须要在代码中显式的建立锁对象,即可以实现锁的机制,并且不用担忧忘记解锁 使用方法极其常见,不作示例了
dispatch_semaphore_t dispatch_semaphore_create(long value);
:建立信号量,传入初始值long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
:当信号<=0时,根据传入的时间阻塞线程;若是信号>0则不阻塞线程,并对信号-1处理long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
:对信号+1处理GCD
为咱们提供的信号量也是经常使用的加锁方式,常见用法是初始化信号值为1
{
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER);
// 操做
dispatch_semaphare_signal(lock);
}
复制代码
常规操做你们都知道,有常规操做,那么必定也有很是规操做,能够看一下AFNetwork
给咱们的示范
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
复制代码
在AFURLSessionManager
中,初始化使用dispatch_semaphore_create(0)
,在return tasks;
前调用dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
阻塞线程,待block将目标值赋值后,执行dispatch_semaphore_signal(semaphore);
,此时tasks已经有值,线程被唤醒后正常返回。很秀
C语言下的互斥锁方案,是协议下四个类的底层
锁经常使用函数:
pthread_mutex_init
:动态初始化互斥量PTHREAD_MUTEX_INITIALIZER
:静态建立互斥量pthread_mutex_lock
:给一个互斥量加锁pthread_mutex_trylock
:加锁,若是失败不阻塞pthread_mutex_unlock
:解锁pthread_mutex_destroy
:销毁锁参数配置函数:
pthread_mutexattr_init
:初始化参数pthread_mutexattr_settype
:设置类型pthread_mutexattr_setpshared
:设置做用域pthread_mutexattr_destroy
:销毁参数条件常见函数:
pthread_cond_init
:动态初始化条件量PTHREAD_COND_INITIALIZER
:静态建立条件量pthread_cond_wait
:传入条件量及锁pthread_cond_signal
:唤醒单条线程并加锁pthread_cond_broadcast
:广播唤醒全部线程pthread_cond_destroy
:销毁条件以上函数都是有返回值的,须要注意的是,若成功则返回0,不然返回错误编号,不是咱们习惯中的成功YES失败NO
锁类型:
PTHREAD_MUTEX_NORMAL
:缺省值,这种类型的互斥锁不会自动检测死锁。若是一个线程试图对一个互斥锁重复锁定,将会引发这个线程的死锁。若是试图解锁一个由别的线程锁定的互斥锁会引起不可预料的结果。若是一个线程试图解锁已经被解锁的互斥锁也会引起不可预料的结果PTHREAD_MUTEX_ERRORCHECK
:这种类型的互斥锁会自动检测死锁。若是一个线程试图对一个互斥锁重复锁定,将会返回一个错误代码。若是试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。若是一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码PTHREAD_MUTEX_RECURSIVE
:若是一个线程对这种类型的互斥锁重复上锁,不会引发死锁,一个线程对这类互斥锁的屡次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能获得这个互斥锁。若是试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。若是一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。这种类型的互斥锁只能是进程私有的(做用域属性PTHREAD_PROCESS_PRIVATE)PTHREAD_MUTEX_DEFAULT
:就是NORMAL类型锁做用域:
PTHREAD_PROCESS_PRIVATE
:缺省值,做用域为进程内PTHREAD_PROCESS_SHARED
:做用域为进程间使用示例:
static pthread_mutex_t c_lock;
- (void)testPthread_mutex {
pthread_mutexattr_t c_lockAttr;
pthread_mutexattr_init(&c_lockAttr);
pthread_mutexattr_settype(&c_lockAttr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutexattr_setpshared(&c_lockAttr, PTHREAD_PROCESS_PRIVATE);
pthread_mutex_init(&c_lock, &c_lockAttr);
pthread_mutexattr_destroy(&c_lockAttr);
pthread_t thread1;
pthread_create(&thread1, NULL, _thread1, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, _thread2, NULL);
}
void *_thread1() {
pthread_mutex_lock(&c_lock);
printf("thread 1\n");
pthread_mutex_unlock(&c_lock);
return 0;
}
void *_thread2() {
pthread_mutex_lock(&c_lock);
printf("thread 2 busy\n");
sleep(3);
printf("thread 2\n");
pthread_mutex_unlock(&c_lock);
return 0;
}
复制代码
互斥量须要时间来加锁和解锁。锁住较少互斥量的程序一般运行得更快。因此,互斥量应该尽可能少,够用便可,每一个互斥量保护的区域应则尽可能大。
互斥量的本质是串行执行。若是不少线程须要领繁地加锁同一个互斥量, 则线程的大部分时间就会在等待,这对性能是有害的。若是互斥量保护的数据(或代码)包含彼此无关的片断,则能够特大的互斥量分解为几个小的互斥量来提升性能。这样,任意时刻须要小互斥量的线程减小,线程等待时间就会减小。因此,互斥量应该足够多(到有意义的地步),每一个互斥量保护的区域则应尽可能的少。
Posix互斥量pthread_mutex_t iOS 常见知识点(三):Lock 再也不安全的 OSSpinLock How does @synchronized lock/unlock in Objective-C? [爆栈热门 iOS 问题] atomic 和 nonatomic 有什么区别? 《高性能iOS应用开发中文版》