多线程(二)方案

在上一篇文章中,了解进程及线程的基础知识,我们在本文探讨在iOS开发中所遇到的多线程方案,以及方案中涉及的相关概念。

一、多线程的方案

image-20181229170008501

以上方案,除去pthread跨平台的方案,其他基于Objective-C苹果独有的方案,我们将在后面系列的文章中逐一阐述。

二、队列与同步异步

2.1 队列

在说明队列之前,先对几个概念再次重温:

  1. 线程用于指代独立执行的代码段,在 iOS 中,线程的底层实现是基于 POSIX threads API 的,也就是我们常说的 pthreads 。
  2. 进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。
  3. 任务(task)用于指代抽象的概念,表示需要执行工作,可以理解为就是一段代码。

队列是一种先进先出(FIFO)线性的数据结构,是计算机系统为了对任务进行调度的一种抽象化机制——在线程 与任务之间搭建起桥梁,即将任务调度分配到具体的线程上。

一个队列由一个或多个任务组成,当这些任务要开始执行时,系统会分别把他们分配到某个线程上去执行。当有多个系统核心时,为了高效运行,这些核心会将多个线程分配到各核心上去执行任务,对于系统核心来说并没有任务的概念。

串行与并发队列顺序处理

至于队列是串行还是并发,区别在于队列执行任务的方式——是否等待当前任务结束。

串行队列 并发队列
是否等待当前任务结束
表现 只能一个接着一个执行任务 可以并发执行任务
线程 每次对应一个线程,这个线程可能不变,也可能被更换 可能对应多个线程,也可能对应一个线程

注意上面两个关键词:只能可以

只能表示串行队列,一定是任务一个一个接着执行,不可能并发。

可以表示任务可以并发执行,但并不一定是并发执行的。因为,还需要看任务是否是同步还是异步提交的。

串行队列

并发队列

并行队列图

2.2 同步与异步

同步和异步操作的主要区别在于是否等待当前任务完成,亦即是否阻塞当前线程。

同步 异步
是否等待当前操作结束
表现 阻塞线程,执行完成才继续之后后续代码 不会阻塞线程,调用后立即返回

针对上面的术语,在iOS中:

image-20181229180327812

三、各种队列的同步异步

下面我们针对各种队列的同步异步,进行代码演练,并进行总结,如下:

image-20181229180634938

需要注意的是:在执行同步任务的过程中,系统会进行优化,一般来说,会将同步操作加入到当前正在运行的线程中去。

3.1 GCD

其中以GCD的方式来进行。

image-20181229180139471

image-20181229180234603

3.2 主队列

iOS 主队列本质是一个串行队列。

3.2.1 主队列同步

1
2
3
4
5
6
7
8
9
10
-(void)syncOperationMainThread {
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"------------Start------------");
for (int i = 0; i < 10; ++i) {
dispatch_sync(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

主队列-同步:崩溃

  1. 依次将任务提交到主队列:syncOperationMainThread、任务1;

  2. 在主线程先执行syncOperationMainThread,其内部需要执行任务1;

  3. 但执行任务1,根据串行队列的特点,又需要执行完syncOperationMainThread;

  4. 相互依赖对方执行完才能继续执行,产生死锁,崩溃。

3.2.2 主队列异步

1
2
3
4
5
6
7
8
9
10
- (void)asyncOperationsOnMainThread {
NSLog(@"------------Start------------");
dispatch_queue_t q = dispatch_get_main_queue();
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

主队列-异步

  1. 依次提交任务到主队列,所以是asyncOperationsOnMainThread,任务1;

  2. 主队列在主线程执行,执行asyncOperationMainThread,其中遇到任务1;

  3. 由于任务1是异步提交的,所以不需要等待任务1返回,可以继续asyncOperationMainThread;

  4. 执行完asyncOperationMainThread,继续在主线程执行异步提交的任务1。

3.3 串行队列

3.3.1 串行队列同步

1
2
3
4
5
6
7
8
9
10
11
- (void)syncOperationsOnSerialThread {
dispatch_queue_t q = dispatch_queue_create("serial",DISPATCH_QUEUE_SERIAL);
NSLog(@"------------Start------------");
for (int i = 0; i < 10; ++i) {
// 同步任务顺序执行
dispatch_sync(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

串行队列-同步

  1. 主线程执行syncOperationsOnSerialThread;

  2. 将任务1同步提交到一个串行队列”serial”,队列”serial”,由于同步 任务不会创建新线程,所以在主线程执行任务1,由于是同步,需要等待执行完;

  3. 任务1执行完,回到主线程继续执行syncOperationsOnSerialThread。

3.3.2 串行队列异步

1
2
3
4
5
6
7
8
9
10
- (void)asyncOperationsOnSerialThread {
dispatch_queue_t q = dispatch_queue_create("serial",DISPATCH_QUEUE_SERIAL);
NSLog(@"------------Start------------");
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

串行队列-异步

  1. 主线程执行asyncOperationsOnSerialThread;

  2. 将任务1异步提交到串行队列,队列将分配新线程执行任务1,由于是异步提交,无须立即返回结果和等待。

    回到主线程继续执行asyncOperationsOnSerialThread;

  3. 执行完asyncOperationsOnSerialThread,新线程依次执行任务1;

3.4 全局队列

全局队列本质上是一个并发队列。

3.4.1 全局队列同步

1
2
3
4
5
6
7
8
9
10
- (void)syncOperationsOnGloabThread {
NSLog(@"------------Start------------");
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; ++i) {
dispatch_sync(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

全局队列-同步

1.在主线程执行syncOperationsOnGloabThread;

2.将任务1提交到全局队列,遇到任务1,由于是同步提交,需要立即执行任务1,阻塞当前主线程;

​ 2.1 原本任务1提交全局队列,是个并发队列,也就是任务1原本可以在多个线程并发执行,但由于同步需要单个任务串行执行;

​ 2.2 而且由于同步操作的优化,原本任务1会在主线程之外的新进程串行进行,此处也会优化成在主线程执行;

3.待任务1执行完之后,继续执行syncOperationsOnGloabThread;

3.4.2 全局队列异步

1
2
3
4
5
6
7
8
9
10
- (void)asyncOperationsOnGloabThread {
NSLog(@"------------Start------------");
dispatch_queue_t q = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

全局队列-异步

1.在主线程执行asyncOperationsOnGloabThread;

2.将任务1提交到全局队列,并且以异步方式提交;

3.异步提交后,继续执行asyncOperationsOnGloabThread;

4.之后开始执行任务1,并且全局队列会调度多个新线程并发执行任务1。

3.5 并发队列

3.5.1 并发队列同步

1
2
3
4
5
6
7
8
9
10
11
- (void)syncOperationsOnConcurrentThread {
dispatch_queue_t q = dispatch_queue_create("concurrent",DISPATCH_QUEUE_CONCURRENT);
NSLog(@"------------Start------------");
for (int i = 0; i < 10; ++i) {
// 同步任务顺序执行
dispatch_sync(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

并发队列-同步

  1. 执行syncOperationsOnConcurrentThread;

  2. 同步提交任务1到并发队列,由于是同步,将会等待任务1执行完毕;

  3. 并且由于优化,将同步任务分配到主线程执行;

  4. 执行完任务1后,继续执行syncOperationsOnConcurrentThread。

3.5.2 并发队列异步

1
2
3
4
5
6
7
8
9
10
11
- (void)asyncOperationsOnConcurrentThread {
dispatch_queue_t q = dispatch_queue_create("concurrent",DISPATCH_QUEUE_CONCURRENT);
NSLog(@"------------Start------------");
for (int i = 0; i < 10; ++i) {
// 同步任务顺序执行
dispatch_async(q, ^{
NSLog(@"任务1 - %@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"------------End------------");
}

并发队列-异步

  1. 执行asyncOperationsOnConcurrentThread

  2. 异步提交任务1到并发队列,由于异步提交,将会继续执行asyncOperationsOnConcurrentThread;

  3. 执行完后,并发队列将任务1分配到多个线程执行任务1

参考

链接

  1. 《深入解析Mac OS X & iOS 操作系统》

  2. Threading Programming Guide

示例代码

  1. 各种队列与同步异步