iOS多线程总结
碎碎念
这是2019的第一篇,虽然原来这篇大概在上年7、8月的时候就开始写了,但是因为各种生活+工作的事情,再加上对文章保持严谨的态度,在写之前写过很多测试代码,同时也顺便自己复习了一下,所以一直搁置到现在才写完。
前言
最初写这一篇,是曾经被抛出过一个很泛的问题,“说说你知道的iOS多线程的知识点?”,对于一个iOS开发者来说,多线程绝对不陌生,而且经常会用到,但是遇到这个问题的时候,很容易想到什么就说什么,给别人一种“不熟悉”的感觉,所以借着这个原因,我也去梳理了一下关于多线程的知识,写下这篇文章,作为记录。
如果没有耐心看完,可以直接看这里。
正文
聊到多线程,肯定少不了要说说进程和线程之间的关系。
什么是进程?
在操作系统中,开启一个任务会开启一个进程。
在iOS上,一个app对应一个进程。每个进程运行在其专用且受保护的内存空间中。
例如在mac上打开终端,然后再输入ps
就会看到开启了一条bash
MacdeMacBook-Pro:~ Fidetro$ ps
PID TTY TIME CMD
8329 ttys000 0:00.03 -bash
如果再开打开一个终端,再次输入ps
就会看到开启了两个bash
MacdeMacBook-Pro:~ Karim$ ps
PID TTY TIME CMD
8329 ttys000 0:00.03 -bash
8651 ttys001 0:00.02 -bash
一个进程中又至少会开启一条线程,在iOS中,我们把那条线程叫做主线程。一个进程可以开启多条线程。
什么是线程?
线程是指给代码单独执行的通道。 —-翻译自苹果文档
iOS中提供了4个方案让我们使用多线程,分别是:
- Pthreads
- NSThread
- GCD
- NSOperation
这篇主要是讲GCD,其他方案的资料,可以参考总结的图。
GCD
GCD是一套C语言编写的接口,其代码也是开源,它为我们提供多线程技术,GCD中最核心的两个概念,任务和队列。
在iOS中使用GCD创建队列,调用dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
第一个参数代表队列的名字,第二参数代表队列的类型,使用DISPATCH_QUEUE_SERIAL
创建串行队列,使用DISPATCH_QUEUE_CONCURRENT
创建并行队列。
串行队列
串行队列,任务按照进入顺序执行。遵循FIFO(先进先出)原则。
并行队列
并行队列,任务会并行执行,无法保证任务完成的时机是添加的顺序。
异步
异步执行不会等待内部代码全部执行完然后返回,而是直接往下执行。
同步
同步执行会等待内部代码执行完毕之后,再往下执行。
同步 | 异步 | |
---|---|---|
串行队列 | 不会创建新的线程,而是在当前线程执行。任务按顺序执行,等待内部代码执行完毕后往下执行。 | 调用dispatch_queue_create() 会创建新的线程,任务按顺序执行,不会等待内部代码全部执行完然后返回,而是直接往下执行。 |
并行队列 | 与串行同步一致。 | 调用dispatch_async() 会创建新的线程,任务在不同的线程下执行。 |
dispatch_after
dispatch_after()
是iOS最常用的延时方法。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//两秒后执行
});
dispatch_group_t
dispatch_group_t
用来监听多个异步任务的完成。
GCD提供了两种方式去获取dispatch_group_t
完成的回调。
第一种是dispatch_group_notify()
,不会阻塞当前线程,在所有任务完成后,回调到block;
第二种是dispatch_group_wait()
,会阻塞当前线程,等待所有任务完成后才往下走,dispatch_group_wait()
不可以在主线程调用,否则会造成死锁。
dispatch_group_notify()
的例子如下:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1");
dispatch_group_leave(group);
});
});
});
dispatch_group_async(group, queue, ^(){
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2");
dispatch_group_leave(group);
});
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
NSLog(@"3");
});
输出结果:
2018-12-19 15:12:27.770146+0800 GCD[70391:2895561] 2
2018-12-19 15:12:29.220584+0800 GCD[70391:2895561] 1
2018-12-19 15:12:29.220987+0800 GCD[70391:2895561] 3
dispatch_group_wait()
例子如下:
dispatch_queue_t asyncQueue = dispatch_queue_create("com.group.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(asyncQueue, ^{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1");
dispatch_group_leave(group);
});
});
});
dispatch_group_async(group, queue, ^(){
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2");
dispatch_group_leave(group);
});
});
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"3");
});
输出结果:
2019-02-16 12:21:12.300569+0800 GCD[79234:3427553] 2
2019-02-16 12:21:13.751251+0800 GCD[79234:3427553] 1
2019-02-16 12:21:13.751419+0800 GCD[79234:3427633] 3
dispatch_barrier_async
dispatch_barrier
相当于一个“栅栏”的作用,在相同的队列中,会先执行在dispatch_barrier
之前的任务,然后dispatch_barrier
之后的任务,都需要等待dispatch_barrier
完成之后,才会执行block里面的内容。
另外苹果文档中有写道:
The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.
使用dispatch_barrier_async
的时候,需要自定义队列才有效,如果使用全局队列或者同步队列,效果和dispatch_sync()
同步函数一样。
//防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。
dispatch_queue_t dataQueue = dispatch_queue_create("com.karim.gcd.dataqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"read data 1");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 2");
});
//等待前面的都完成,在执行barrier后面的
dispatch_barrier_async(dataQueue, ^{
NSLog(@"write data 1");
[NSThread sleepForTimeInterval:1];
});
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:1.f];
NSLog(@"read data 3");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 4");
});
输出结果如下:
2018-12-19 15:26:50.297477+0800 GCD[70716:2943544] read data 2
2018-12-19 15:26:52.302570+0800 GCD[70716:2943541] read data 1
2018-12-19 15:26:52.302925+0800 GCD[70716:2943541] write data 1
2018-12-19 15:26:53.306632+0800 GCD[70716:2943544] read data 4
2018-12-19 15:26:54.307808+0800 GCD[70716:2943541] read data 3
总结
我把一些关于多线程的知识点整理在这个图里了,如果有遗漏,可以联系我,我会即时补充上去。
参考
请保持转载后文章内容的完整,以及文章出处。本人保留所有版权相关权利。