提到多线程,我们总是绕不开这些个概念:同步 & 异步,队列,串行队列 & 并行队列 。
概念
我们不妨先来了解下这些个概念:
- 同步:阻塞线程等待,当前线程执任务,直至执行完成。
- 异步:不阻塞线程线程安全,可开启新线程执行任务,继续执行嵌套循环后续任务。
- 队列:似管道,一头进,一头出嵌套分类汇总怎么做,一个接一个,不能并行,不能插队。
- 串行队列:管子的出口,放出去一个后,必须等待这个完事儿了,再放下一个,就这样线程数越多越好吗一个接一个直至全放完。
- 并行队列:还是那根管子,放一个后,可以紧接着放下一个,下两个。。。取决于线程数,直至字符全放完。
从上面的总结能看出来他们的区别:
- 同步 & 异步的数据处理英文区别主要在于:是否会阻塞当前线程,是否会开启新的线程。
- 串行 & 并行的区别主要在于:放出去一个之后线程数是什么,是多线程的实现方式等待完线程数是什么成还是紧接着多线程编程继续放。
下面,我们就来看看上面这些概念,怎么在 GCD
中来演绎。
基本操作
队列创建
我们先来看看 GCD
中线程和进程的区别是什么所谓的队列,它是通过下面的方法创多线程建:
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
可以看到,其包含有多线程并发两个参数,分别为线程数越多越好吗何意?官方文档 给了解释:
- label:一个标签,字符串类型,附加到队列上的,有啥用呢?方便调试的时候去分辨去标识。并且推荐了命名方式(
c线程数越多越好吗om.example.myqueue
)。注意,这个参数是可选的线程的几种状态,可以是NULL
。 - attr:嵌套是什么意思指定队列类型,
DISPATCH_QUEUE_SERIAL嵌套结构
(orNULL
)为串行,DISPATCH_QUEUE_CONCURRENT
为并行。
So,我们可以像这样去创建我们的队多线程的实现方式列:
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
同步 & 异步
队列有了,我们就可以往里面塞任务了,怎么塞呢?
比如,我们可以像这样 同步多线程并发 塞:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
还可以多线程并发像这样 异步 塞:
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
这里实际上就是组合情况了,因为这里的 queue
可能是 串行队列 或者 并行队列。
排列组合
排列组合线程是什么意思的结果可能就会有下面这些情况,那么我们可以分析一下了:
- 同步 + 串行队列:没什么悬念,阻塞当前线程一个接一个任务执行,队列中国呢所有任务执行完成之后,再接着执行后面的代码。
- 同步 + 并行队列:虽然这里嵌套是什么意思是并行队列,但是因为是同步,阻塞当前线程,并不会开启新的线程,所以队列分发一个任务之后,并没有线程可供它继续分发后字符型变量一个任务,只能等待直到前面任务执行完成,所以跟上面结果实际上是一致的。
- 异步 + 串行队列:这里是异步执行,也就数据处理工程师是会开启一个新线程去执行串行队列分发的任务,而当前线程会继续执行后续代码,尽管如线程池面试题此,队列里的任务还是会等待前面的任务执行完成后再次字符间距在哪里设置进行分发,这不是线程的问题了,是串行队列的特性,多线程并发中线程的状态最终的结果是队列里的任务还是一个接一个执行,但是是在新开启的线程中,并非当前线程。
- 异步 + 并行队列:到这里情况就会变得不一样了,因为是异步就不会阻塞数据处理英文在当前线程执行任务,会在并行队列分发一个任字符串是什么意思务的时候,开启一个线程去执行,而之后呢?并行队列会继续分发,然后继续开启线程去执行,直到队列里面额嵌套是什么意思任务全部分发完成。需要注意的是,因为字符间距怎么加宽分发时候并不是等待前面任务执行完成的,所线程池以所有任务执行的先后顺序就会变得不确定起来。
总结来看,可以有这么几条:数据处理方法
- 有 同步 或者 串行队列 队列线程池里面的任务就会按照顺序去执行;
- 是 同步 就不会开启新线程,异步 才数据处理方法会开启线程;
- 线程的开启条数取决于队列给的任务数;
!! 这里需要思考的一个问题是嵌套循环:异步 + 并行队列 情况下,倘若并行队列里面的任务很多很多,那这时候会开启多少个线程呢?肯定的是最大值不可能是无穷大,那多少是个额定值呢?达到嵌套if函数这个额定值后,并行队列还分发任务么?暂时先留个疑问。
嵌套组合
实际上,上面的组合并没有覆盖所有线程的几种状态的日常使用场景,比如嵌套查询sql语句 嵌套 使用的情况。
怎么个意嵌套是什么意思思呢?在上面 同步 &am嵌套分类汇总怎么做p; 异步 执行的任务代码,可能又是上面的排列组合情况,具体如下面这样:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
});
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
// 同步执行任务创建方法
dispatch_async(queue, ^{
// 这里放同步执行任务代码
});
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
// 同步执行任务创建方法
dispatch_async(queue, ^{
// 这里放同步执行任务代码
});
});
这时候会发生什么?注意这里的 queue
依旧可能是 串行队线程池的七个参数列 或者 并行队列,那么可能的情况就是这样的(&
表嵌套):
- 同步 + 串行队列 & 同步 + 串行队列
- 同步 + 串行队列 & 异步 + 串行嵌套if函数队列
- 同步 + 并行队列 &线程的几种状态amp; 同步 + 并行嵌套函数队多线程并发列
- 同步 + 并行队列 & 异步 + 并行队列
- 异步 + 串行队列 & 同步 + 串行队列
- 异步 + 串行队列 & 异步 + 串行队列
- 异步 + 并行队列 & 同步 + 并行队嵌套循环列
- 异步 + 并行队列 & 异步 + 并行队列
看似情况变得很复杂,嵌套if函数实际上只要抓住文章最前面提到数据处理软件有哪些的概念特性,很容易厘清。这里最重要的就是 同步 的阻塞执数据处理能力行及 串行队列 的阻塞字符派发,所以涉及到的组合情况就需要格外注意一下。
假设:外层任务记为 A,嵌套任务记为 B。
对于情况 1、2,在 同步 + 串行队列 中,因为队列依旧是串行队列,所以若是 同步 去执行嵌套的任务 (情况 1),会因其阻塞当前线程而导致任务 A 没法结束,从而 串行队列 无法派发任务 B,相互等待导致 死锁。若是 异步字符串逆序输出 (情况 2),因为不阻塞线程则会使之能顺利执行完任务 A,并在结束后开启新的线程来执行 串行队列 再次派发的任务 B(情况 2),这样,这种情况也会保证任务的执行顺序了(先 A 后 B)。
对于情况 3、4,在 同步 + 并行队列 中,因为此时变成了 并行队列,任务的分发不依赖前面任务的结束,所以 同步 执行嵌套任务的时候(情况 3),该队列会再次派发一个任务 B,需要注意的是,这时多线程并发中线程的状态候就需要任务 B 执行完成之后字符型变量,再继续外层任务的数据处理执行(同步的阻塞线程特性)(先 B 后 A)。而对于 异步 执行嵌套任务呢(情况 4)?队列依旧会数据处理英文派发任务 B,但是不会阻塞完成任务的执行了,会开启线程执行任务 B(先 A 后 B)。
对于情况 5、6,在 异步 + 串行队列 中,实际上情况跟 1、2 是一致的,这里的 异步 影响的是任务 A 的执行需多线程面试题要开启新的线程,嵌套内的情况并无变化。
对于情况 7、8,在 异步 + 并行队列 中,实际上情况跟 3、4 是一致的,同样这里的 异步 影响的是任务 A 的执行需要开启新的线程,嵌套内的情况并无变化。
所以,是嵌套if函数不是并没有想象的乱线程撕裂者套?理解其特性之后,复杂的情况就会变清晰很多。
那么问题来了,上面的嵌套情况队列都是保持一致的嵌套查询,那不一致的情况呢?或者再继续嵌套呢?留给大家一起分析分析,实际上都差不多,厘清之后再 coding 的时候,就线程池面试题不慌了。
主队列
这里有必要在提数据处理活动不包括以下一下这个队列 — 主队列。
它是系统自己创数据处理英文建的,我们只能获取,其获取方法是这个:
dispatch_queue_main_t dispatch_get_main_queue(void) {
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
看着是一个很特殊的队列,实际上他就是一个普多线程通的嵌套结构 串行队列,只是他的任务是在 主线程 上执行的。
就像所谓的 主线程 一样,并不是这个线程特殊,只是将其标记为 主线程 而已,因为多线程是什么意思任务的执行有优先级,线程也需要有主次。
具体的执行情况线程池面试题,可以直接参照 串行队列 就可以了。
线程间通信
线程间通信,或者叫线程间跳转,实际上还是我们前面说到的 嵌套。
只是这里的 queue
可能不是同一个了。
比如,我们常见的场景,子线程处理数据后主线程显示:
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
sleep(2); // 模拟数据处理
// 回到主线程
dispatch_async(mainQueue, ^{
// 在主线程模拟刷新UI
});
});
这里涉及到的就是 queue
跟 mainQueue
的切换。
我们在切换过程中需要格外注意的就是:
- 避免相互等待造成的死锁;
- 避免任务执行顺序紊乱数据处理英文不符合预期;
常用函数
需求的场景总多线程面试题是复杂的,为了保证完美切合需求的场景,GCD
也是封装了一系列边便捷嵌套循环的方式方法。
栅栏函数
我们可能会有这么一个需求的场景,任务 1,2 是可以并发的&无序的,任务 3,4 是可以并发的&无序的,但是任务 A 需要在 1,2 完成之后,并且 3,4 开始之前。
这里我们就可以用 栅栏函嵌套是什么意思数 来轻松解决,比如这样:
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 异步追加任务 1
dispatch_async(queue, ^{
// 模拟耗时操作
sleep(2);
});
// 异步追加任务 2
dispatch_async(queue, ^{
// 模拟耗时操作
sleep(2);
});
// 通过 栅栏函数 追加任务 A
dispatch_barrier_async(queue, ^{
// 模拟耗时操作
sleep(2);
});
// 异步追加任务 3
dispatch_async(queue, ^{
// 模拟耗时操作
sleep(2);
});
// 异步追加任务 4
dispatch_async(queue, ^{
// 模拟耗时操作
sleep(2);
});
所嵌套循环以,栅栏函数 就好多线程并发中线程的状态比是 栅栏,就此分割,从这里拦字符间距加宽2磅住任务的执行,前后的字符不管数据处理活动不包括以下哪种情形,管的是数据处理能力栅栏任务的前与后。
一嵌套if函数次性函数
所谓的一次性函数,就是保证在 APP 进程中,只会执行一次的函数,而且是线程安全的。
这在我们日常开发中也是有对应的场景的,比如 单例,通常像下面这样:
+ (instancetype)shareInstance {
static SomeClass * singleton = nil;
static dispatch_once_t onceToken;
// 通过一次性函数创建单一对象
dispatch_once(&onceToken, ^{
singleton = [[SomeClass alloc] init];
});
return singleton;
}
延时执行函数
我们通常也有这样的需求场景,某一任务的执行,需要在一定的延时之后,我们就可以通过 GCD
的延时函数来办:
// 2.0 秒后异步追加任务代码到主队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 模拟耗时操作
sleep(2);
});
需要注意的是:这里的延时 2 秒,并不是 2 秒后开始执行任务,这多线程下载里只是 2 秒后添加到指定的队列中,具体的执行需要由队列的派发决定。
快速迭代函数
我们通常遍历的时候会用 for
之类的循环去实现,而 GCD
给我们提供了另外一种方式:
void dispatch_apply(size_t iterations,
dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
DISPATCH_NOESCAPE void (^block)(size_t iteration));
这里涉及到线程三个参数:
- iterations:迭代的次数
- queue:任务指定的队列
- block:回调添加任务
我们可以像下面这样:
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 通过迭代函数添加任务
dispatch_apply(6, queue, ^(size_t index) {
// 追加需要执行的任务
});
// ⚠️ 注意:这里会等待上面所有任务执行完成后,再继续后面的代码执行
// some things
这里值得一提的是,dispatch_apply()
会阻塞等待所有任务的执行完成。而这,有时候数据处理包括哪些内容正是我们需要的。
队列组
上面的 快速迭代函数 是会阻塞等待的,那么有没有不阻塞线程也能实现等待所有异步任务完成后再去执行后续任务呢?
显然是有的,这不是来了么?
比如,我们一字符串是什么意思个页面有多个请线程是什么意思求的时候,我们如果想要等待所嵌套有的请求返回后再去刷新 UI 的话,可以这样:
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建一个组
dispatch_group_t group = dispatch_group_create();
// 向组内添加请求任务 1
dispatch_group_async(group, queue, ^{
// 模拟请求耗时
sleep(2);
});
// 向组内添加请求任务 2
dispatch_group_async(group, queue, ^{
// 模拟请求耗时
sleep(2);
});
// 等前面的异步任务 1、任务 2 都执行完毕后 通知回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 主线程中刷新 UI
});
// 后续任务不阻塞
// some things
信号量
当我们面对一堆杂乱的异步任务,比如章前面提到的 异步 + 并发数据处理能力队列,我们能让它们有序的去执行么?
它,信号量,就能搞!
在这之数据处理包括数据的收集存储使用前我们来看看 信号量 相关的几个函数:
// 创建一个计数信号量,value 为初始值
dispatch_semaphore_t dispatch_semaphore_create(intptr_t value);
// 信号量计数+1,dsema 为信号量
intptr_t dispatch_semaphore_signal(dispatch_semaphore_t dsema);
// 信号量计数-1,dsema 为信号量,timeout 为超时时间
intptr_t dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
需要注意一下的是:
-
dispatch_semaphore_create()
创建一个多线程是什么计数信号量,参数为初始值。 -
dispatch_semaphore_signal()
执行后,信号量+1
,线程撕裂者同时会判断信多线程应用场景例子号量增字符间距加宽2磅加前值是否小于 0(即增加后值是否小于等于0),若是则唤醒等待的线程去判断是否继续等待,并嵌套分类汇总怎么做返回非零,否则不唤醒并返回 0. -
dispatch_sem线程和进程的区别是什么aphore_wait()
会先把信号量-1
,然后判断其是否小于 0,小于 0 则等待,否则结束等待,返回 0 (成功)。另外,若出错,返嵌套分类汇总怎么做回值是非零线程池。
OK,下面我们来模字符间距在哪里设置拟一下前面提到的场景实现:
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建一个计数信号量,初始值为 0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 添加异步请求任务 1
dispatch_async(queue, ^{
// 模拟请求耗时
sleep(2);
dispatch_semaphore_signal(semaphore);
});
// 等待前面异步请求完成
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 添加同步请求任务 2
dispatch_sync(queue, ^{
// 模拟请求耗时
sleep(2);
});
// 等待前面异步请求完成,同步的无需通过 dispatch_semaphore_wait()
// 添加异步请求任务 3
dispatch_async(queue, ^{
// 模拟请求耗时
sleep(2);
dispatch_semaphore_signal(semaphore);
});
// 等待前面异步请求完成
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 后续任务会阻塞
// some things
上面就能保证异步任务1、2、3 能按照顺序执行了。为什么?我们来分析一下。
起初,信号量为 0,字符间距在哪里设置接着添加了任务 1,因为是异步,并不阻塞,走到了 dispatch_semaph字符间距加宽2磅怎么设置ore_wait()
,信号量自减为 -1
,等待直到任务 1 执行完成后调用 dispatch_semaphore_signal(sema数据处理包括数据的收集存储使用phore)
,信号量自增为 0
,唤醒等待线程,判断为非小于零,继续后面的逻辑,添数据处理加同步任务 2,阻塞线程执行完同步任务 2 后,在添加异步任务 3字符间距怎么加宽,重复任务 1 的逻辑,这样就保证了其字符间距顺序。
当然,信号量的应用并不是仅限于此,实际上使用也很简单,但是还是要理解其函数的具体含义,以便灵活运用。
总结
截至数据处理的最小单位目前,线程池的七个参数GCD
里面的一些基本概念我们就聊明白了,笔者认为,线程数越多越好吗理解还是首要的,死记硬背不可取,会忘也会乱,线程和进程的区别是什么刨根问底透彻理解其逻辑及含义,在 coding 的时候我们就能根据实际的需求场景灵活应用了。多线程下载
以上嵌套if函数,希望我们都能有所收获。