1 概念
1.1 什么是多线程
1个进行中可以开发多个线程,多个线程可以“同时”执行不同的任务;可以解决程序阻塞的问题;多线程可以提高程序的执行效率
1.2 多线程执行原理
(单核CPU) 同一时间,cpu只能处理1个线程,只有1个线程在执行
多线程同时执行:是CPU快速的在多个线程之间的切换
cpu调度线程的时间足够快,就造成了多线程的“同时”执行
如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源 (一般开3-6个线程)
每个线程调度的次数会降低,线程的执行效率降低
1.3 多线程优/缺点
优点:
能适当提高程序的执行效率
能适当提高资源的利用率(cpu,内存)
线程上的任务执行完成后,线程会自动销毁
缺点:
开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512KB)
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多, cpu 在调用线程上的开销就越大
程序设计更加复杂,比如线程间的通信、多线程的数据共享
1.4 主线程
主线程:一个程序运行后,默认会开启1个线程,称为“主线程” 或 “UI线程”;主线程一般用来 刷新UI界面,处理UI事件(比如: 点击、滚动、拖拽等事件),主要处理与用户的交互。
主线程使用注意:别将耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验
1.5 iOS 同步/异步执行
- 同步执行:比如dispatch_sync,这个函数会把一个 block 加入到指定的队列中,而且会一直等到执行完 block,这个函数才返回。因此在 block 执行完之前,调用 dispatch_sync 方法的线程是阻塞的。
- 异步执行:比如dispatcy_async,这个函数会把一个 block 加入到指定的队列中,但是和同步执行不同的是,这个函数把 block 加入队列后不等 block 的执行就立刻返回了。
注意:
- dispatch_async 和 dispatch_sync 作用是将 block 添加进指定的队列中。并根据是否为 sync 决定调用该函数的线程是否需要阻塞。
- 这里调用该函数的线程并不一定执行 block块,任务的执行者是 GCD 分配给任务所在队列的线程。
- 结论:调用 dispatch_sync 和 dispatch_async 的线程,并不一定是 block 的执行者。
1.6 iOS 串行/并发队列
- 串行队列:比如 dispatch_get_main_queue,这个队列中所有任务,一定按照FIFO(先进先出)执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务都已经执行完成。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码。
- 并行队列:比如 dispatch_get_global_queue,这个队列中的任务是按照FIFO(先进先出)开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。并发队列中的任务:GCD会动态分配多条线程来执行。具体几条线程取决于当前内存使用状况,线程池中线程数等因素。
1.7 iOS 中多线程的技术方案
2 pthread
2.1 pthread 方法
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,const pthread_attr_t * _Nullable __restrict,void * _Nullable (* _Nonnull)(void * _Nullable),void * _Nullable __restrict);
参数:
- 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
- 用来设置线程属性
- 线程运行函数的起始地址
- 运行函数的参数
返回值:
- 若线程创建成功,则返回0
- 若线程创建失败,则返回出错编号
2.2 __bridge 桥接
__bridge 桥接 用于告诉编译器如何管理内存
3 NSThread
创建
方式1:NSThread *thread = [NSThread alloc] initWithTarget ….. 创建线程
[thread start]; 执行线程
方式2:[NSThread detachNewThreadSelector…….] // 创建并执行
方式3:[ self performSelectorInBackground……] // 创建并执行
线程的状态
4 GCD
4.1 Dispatch Queue 执行处理的等待队列
两种 Dispatch Queue:
Serial Dispatch Queue 等待现在执行中的处理
Concurrent Dispatch Queue 不等待现在执行中的处理
4.2 创建 Dispatch Queue
两种方式:
第一种:dispatch_queue_create
第二种:获取系统提供的 Dispatch Queue
dispatch_get_main_queue()
dispatch_get_global_queue(0, 0)
4.3 dispatch_set_target_queue 修改优先级
优先级:
DISPATCH_QUEUE_PRIORITY_HIGH : 高
DISPATCH_QUEUE_PRIORITY_DEFAULT : 默认
DISPATCH_QUEUE_PRIORITY_LOW : 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND :后台
4.4 dispatch_after 指定时间后追加处理到 Dispatch Queue
与 dispatch_time 结合使用
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
});
// 合并为一条语句
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
4.5 Dispatch Group 线程组
用于:追加到 Dispatch Queue 中的多个处理全部结束后想执行结束处理
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"blk0"); });
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"blk1"); });
// 使用 Dispatch Group 可监视这些处理执行的结束,一旦检测到所有处理执行结束,就可将结束的处理追加到 Dispatch Queue中
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done"); });
// 也可使用
// dispatch_group_wait :仅等待全部处理执行结束,第二个参数 等待的时间(dispatch_time_t 类型 ; DISPATCH_TIME_FOREVER :永久等待)
// 返回结果:0:全部处理执行结束。 非0:经过了指定的时间,但属于 Dispatch Group 的某个处理还在执行中
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
4.6 dispatch_barrier_async
使用 dispatch_barrier_async 添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_barrier_async(queue, blk_for_writing);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
4.7 dispatch_sync 同步
将指定的 Block “同步”追加到指定的 Dispatch Queue中。在追加 Block 结束之前, dispatch_sync 函数会一直等待。
使用场景:
例:执行 Main Dispatch Queue 时,使用另外的线程 Global Dispatch Queue 进行处理,处理结束后立即使用所得到的结果。这种情况就要使用 dispatch_sync函数。
注意事项:
dispatcy_sync 是简易版的 dispatch_group_wait ,dispatcy_sync 容易引起死锁。
死锁代码:
// 1种:在主线程执行 该句,会造成线程死锁
// 在主线程 中执行指定的 Block, 并等待其执行结束。而其实 在主线程中下在执行这些源代码,所以无法执行追加到 主线程 的 Block
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Hello"); });
// 2种:在主线程执行 该句,会造成线程死锁
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Hello");});
});
// 3种:Serial Dispatch Queue 也会引起死锁
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.serialdispatchQueue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"Hello"); });
});
// 4种:
// dispatch_barrier_sync 函数的作用是在等待追加的处理全部执行结束后,再追加处理到 Dispatch Queue 中,此外,它还与 dispatch_sync 函数相同,会等待追加处理的t执行结束
4.8 dispatch_apple
dispatch_apple 函数是 dispatch_sync 函数和 Dispatch Group 的关联 API。 该函数按指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
// 最后输出的 done
NSLog(@"done");
4.9 dispatch_suspend / dispatch_resume
dispatch_suspend 函数 挂起指定的 Dispatch Queue
dispatch_resume 函数 恢复指定的 Dispatch Queue
4.10 Dispatch Semaphore
4.11 dispatch_once
保证在应用程序执行中只执行一次指定处理。一般用于 生成单例模式对象时使用
4.12 Dispatch I/O
5 NSOperation
5.1 NSOperation 简介
- 作用:配合使用 NSOperation 和 NSOperationQueue 能实现多线程编辑
- 具体步骤:
(1)先将需要执行的操作封装到一个 NSOperation 对象中
(2)然后将 NSOperation 对象添加到 NSOperationQueue 中
(3)系统会自动将 NSOperationQueue 中的 NSOperation 取出来
(4)将取出的 NSOperation 封装的操作放到一条新线程中执行- NSOperation 的子类
- NSInvocationOption
- NSBlockOperation
- 自定义子类继承 NSOperation 实现内部相应的方法
- NSOperationQueue
NSOperationQueue的作⽤:NSOperation可以调⽤start⽅法来执⾏任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中,自动执行操作,自动开启线程
5.2 NSInvocationOperation
// NSOperation 中 : 操作 -> 异步执行的任务; 队列 -> 全局队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
//将操作添加到队列,会"异步"执行 selector 方法
// [queue addOperation:op];
// start方法 会在当前线程执行 @selector 方法
[op start];
5.3 NSBlockOperation
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 第 1 种
NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thread = %@", [NSThread currentThread]);
}];
[queue addOperation:blockOp];
// 第 2 种 直接添加block
[queue addOperationWithBlock:^{
NSLog(@"thread = %@", [NSThread currentThread]);
}];
// addExecutionBlock
//NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的
NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thread = %@", [NSThread currentThread]);
}];
[blockOp addExecutionBlock:^{
NSLog(@"1 execution thread = %@", [NSThread currentThread]);
}];
[blockOp addExecutionBlock:^{
NSLog(@"2 execution thread = %@", [NSThread currentThread]);
}];
[blockOp start];
线程间通讯
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperationWithBlock:^{
NSLog(@"耗时操作 %@", [NSThread currentThread]);
// 主线程更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新 UI %@", [NSThread currentThread]);
}];
}];
5.4 最大并发操作数
并发数:同时执行的任务数
最大并发数:同一时间最多只能执行的任务的个数
注意:如果没有设置最大并发数,那么并发数的个数是由系统内存和CPU决定。最大并发数一般以 2~3为宜。
// 设置最大并发数
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 2;
[queue setMaxConcurrentOperationCount:2];
5.5 暂停 & 继续
- 队列挂起,当前“没有完成的操作”,是包含在队列的操作数中的
- 队列挂起,不会影响已经执行操作的执行状态
- 队列一旦被挂起,再添加的操作不会被调度
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 暂停和恢复 YES:暂停队列 NO:恢复队列
queue.suspended = !queue.suspended;
// 当前状态
BOOL b = queue.isSuspended;
5.6 取消全部操作
- 取消队列中所有的操作
- 不会取消正在执行中的操作
- 不会影响队列的挂起状态
// 1. 取消所有操作
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 取消对列中的所有操作,同样不会影响到正在执行中的操作!
[queue cancelAllOperations];
// 2. 取消单个操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
[queue addOperation:op];
[op cancel];
5.6 操作优先级
优先级的取值: 优先级高的任务,调用的几率会更大
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
// 设置操作的优先级
[op setQueuePriority:NSOperationQueuePriorityLow];
5.7 依赖关系 addDependency
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登录 %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付费 %@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户 %@", [NSThread currentThread]);
}];
// 通过设置依赖来保证执行顺序
[op2 addDependency:op1];
[op3 addDependency:op2];
[op4 addDependency:op3];
// 注意不要循环依赖
// [op1 addDependency:op4];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue 队列是否阻塞当前线程
[queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
[[NSOperationQueue mainQueue] addOperation:op4];
NSLog(@"come here");
6 NSOperation 与 GCD 的对比
- GCD
- 将 block 添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
- GCD 是底层的 C 语言构成的 API
- 在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
- 要停止已经加入 queue 的 block 需要写复杂的代码
- 需要通过 Barrier 或者同步任务设置任务之间的依赖关系
- 只能设置队列的优先级
- 高级功能:
一次性 once
延迟操作 after
调度组
- NSOperation
- 核心概念:把 操作(异步) 添加到 队列(全局的并发队列)
- OC 框架,更加面向对象,是对 GCD 的封装
- Operation 作为一个对象,为我们提供了更多的选择
- 可以随时取消已经设定要准备执行的任务,已经执行的除外
- 可以跨队列设置操作的依赖关系
- 可以设置队列中每一个操作的优先级
- 高级功能:
最大操作并发数(GCD不好做)
继续/暂停/全部取消
跨队列设置操作的依赖关系