iOS中常见的多线程方案
控制器命令:
c:过掉
step:输出汇编
stepi:进入这个汇编函数 简称si
- NSThread、GCD、NSOperation的底层实现其实都是pthread
GCD的常用函数
GCD的队列
容易混淆的术语:同步、异步,并发、串行
各种队列的执行效果
// dispatch_sync和dispatch_async用来控制是否要开启新的线程
/**
队列的类型,决定了任务的执行方式(并发、串行)
1.并发队列
2.串行队列
3.主队列(也是一个串行队列)
*/
面试题
1、下列问题是否会产生死锁
- (void)interview01
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
// dispatch_sync立马在当前线程同步执行任务
}
- (void)interview02
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
// dispatch_async不要求立马在当前线程同步执行任务
// 打印顺序:任务1-3-2
}
- (void)interview03
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
// block0等block1执行完毕,block1等block0执行完毕
// 打印顺序:任务1-5-2-死锁
}
- (void)interview04
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
// 打印顺序:任务1-5-2-3-4
}
- (void)interview05
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
- 队列的特点:排队,FIFO,First In First Out,先进先出
- dispatch_sync:立马在当前线程执行任务,执行完成才能继续往下执行
- dispatch_async不要求立马在当前线程同步执行任务
2、下面代码的打印结果是什么?
// 这句代码的本质是往Runloop中添加定时器
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
// 这句代码本质就是Runtime调用objc_msgSend
[self performSelector:@selector(test) withObject:nil];
3、下面打印结果是什么?
- (void)test
{
NSLog(@"2");
}
// 打印结果为1,因为[thread start]启动会运行当前block,输出结果为1,但是输出以后,线程也就自动退出了,也就是没有子线程了,所以thread=nil,如果想继续执行的话 在block里面启动RunLoop。
// [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
// [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
GNUstep
- GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍
- 源码地址:http://www.gnustep.org/resources/downloads.php
- 虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值
队列组的使用
思考:如何用gcd实现以下功能
异步并发执行任务1、任务2
等任务1、任务2都执行完毕后,再回到主线程执行任务3
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
// 添加异步任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1-%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2-%@", [NSThread currentThread]);
}
});
// 等前面的任务执行完毕后,会自动执行这个任务
// dispatch_group_notify(group, queue, ^{
// dispatch_async(dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任务3-%@", [NSThread currentThread]);
// }
// });
// });
//也可以直接把主队列放进去
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任务3-%@", [NSThread currentThread]);
// }
// });
// 也可以执行完任务1、2 再去执行任务3、4
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3-%@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务4-%@", [NSThread currentThread]);
}
});
多线程安全隐患
多线程安全隐患示例01-存钱取钱
存钱取钱代码演示
@property (assign, nonatomic) int money;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self moneyTest];
}
/**
存钱、取钱演示
*/
- (void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
/**
存钱
*/
- (void)saveMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
取钱
*/
- (void)drawMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
多线程安全隐患示例02-卖票
卖票代码演示
/**
卖1张票
*/
@property (assign, nonatomic) int ticketsCount;
- (void)saleTicket
{
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}
/**
卖票演示
*/
- (void)ticketTest
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; 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)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self ticketTest];
}
多线程安全隐患分析
多线程安全隐患解决方案
iOS 中的线程同步方案
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
OSSpinLock
os_unfair_lock
pthread_mutex 普通锁
pthread_mutex--递归锁
递归锁:允许同一个线程对一把锁进行重复加锁
pthread_mutex--条件锁
NSLock、NSRecursiveLock
NSCodition
NSConditionLock
dispatch_queue
dispatch_semaphore
@synchronized
所有示例代码Demo
iOS线程同步方案性能比较
GCD、OperationQueue 对比
GCD的核心概念:将 任务(block) 添加到队列,并且指定执行任务的函数。
NSOperation 的核心概念:把 操作(异步) 添加到 队列。
区别
GCD:
将任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
GCD是底层的C语言构成的API
iOS 4.0 推出的,针对多核处理器的并发技术
在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
要停止已经加入 queue 的 block 需要写复杂的代码
需要通过 Barrier(dispatch_barrier_async)或者同步任务设置任务之间的依赖关系
只能设置队列的优先级
高级功能: dispatch_once_t(一次性执行, 多线程安全); dispatch_after(延迟); dispatch_group(调度组); dispatch_semaphore(信号量); dispatch_apply(优化顺序不敏感大体量for循环);
OperationQueue:
OC 框架,更加面向对象,是对 GCD 的封装。
iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层进行了全部重写。
可以设置队列中每一个操作的 QOS() 队列的整体 QOS
操作相关 Operation作为一个对象,为我们提供了更多的选择: 任务依赖(addDependency),可以跨队列设置操作的依赖关系; 在队列中的优先级(queuePriority) 服务质量(qualityOfService, iOS8+); 完成回调(void (^completionBlock)(void)
队列相关 服务质量(qualityOfService, iOS8+); 最大并发操作数(maxConcurrentOperationCount),GCD 不易实现; 暂停/继续(suspended); 取消所有操作(cancelAllOperations); KVO 监听队列任务执行进度(progress, iOS13+);
自旋锁跟互斥锁比较
atomic
/*
nonatomic和atomic
atom:原子,不可再分割的单位
atomic:原子性
给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作,也就是保证setter和gette内部是线程同步的
atomic跟nonatomic 在取值跟设值的时候不同,atomic取值设值会加锁(自旋锁)
atomic经常在Mac使用,太耗内存
*/