最近在看《 Objective-C 高级编程: iOS 与 OS X 多线程和内存管理》这本书。书中介绍了部分多线程的知识,总体感觉这本书还是比较深入的讲解一些 iOS 中 Block、多线程等知识的原理和底层实现。对于底层实现的一些源码阅读起来还是比较吃力,这本书还是值得反复去阅读的。

多线程的程序可以在某个线程和其他线程直线反复多次进行上下文切换,因此就好像1个CPU核能够并列地执行多个线程一样。

以上的就是书中所提到的多线程编程的技术。
在操作系统中,我们可以了解到线程其实是一种逻辑上的概念(将进程再次分为若干个 ‘独立’ 的执行块)。这些执行块通过CPU的调度来完成任务。

注:‘独立’ 加引号是因为对于各个线程它们有自己独立资源(比如:ID,寄存器,程序计数器,栈等),还有一部分是共享的资源(比如:代码段,数据段等)

下面来区分几个概念:异步、同步、并行、串行。

异步/同步

异步和同步是相对的,我们通常所了解的异步和同步的区别:
异步: 不需要等待调用函数的返回可以直接执行下面的代码
同步: 等待调用函数的返回才可以直接执行下面的代码

对于同步/异步感性的认识就是手机页面下载大量的图片,下载操作如果为同步操作的话,手机页面可能会产生顿卡;如果使用异步操作,相对来说不会产生顿卡。

这里主要讲一下 异步多线程 这两个概念,在看书的时候一直有写不明白,这里讲讲的我理解。

首先。异步和多线程是有交集。这里交集的意思就是异步的效果可以使用多线程的方式达到;简单的说,多线程是实现异步的一种方式,但不是唯一的一种。

借用网上的一句话:异步是目的;多线程是手段

阅读了一些网上的文章。对于异步还有一种理解就是不消耗 CPU 资源,比如:数据的 I/O,网络数据请求和发送等。为什么这些操作不消耗 CPU 资源?涉及到硬件方面的问题,这里不详细展开。对于数据 I/O 来说,有特殊的硬件模块支持,CPU 只要发送相应的指令给控制器,对于数据的读写有相应的模块去完成,CPU 可以继续做其他事情。

小结

  • 多线程只是异步的一种实现,但是不是唯一的实现。
  • 特殊的异步操作(I/O)是有特殊的硬件支持。

并行/串行

区别:

并行: 假设开了10个线程,这10个线程同时开始执行,这种方式叫并行
串行: 这10个线程按顺序逐个执行,这种方式叫串行。

对于多线程来说,并行技术是提高其效率的主要因素。而如果采用串行的方式,反而在线程调度的时候浪费了资源。

GCD (Grand Central Dispatch)

GCD 是 Apple 提供的一种多线程解决方案之一。GCD 生成必要的线程并执行任务,可以有效的管理线程。

GCD 是异步执行任务的技术之一。

最常见的一个 GCD 代码栗子。

1
2
3
4
5
6
7
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//处理耗时代码。

dispatch_async(dispatch_get_main_queue(), ^{
// 主线程任务出处理
});
});

对于 Dispatch Queue 的种类可以分为两类

Dispatch Queue 说明
Serial Dispatch Queue 等待现在执行处理结束
Concurrent Dispatch Queue 不等待现在执行处理结束

下面来介绍系统给我们提供的一些 GCD API。

创建一个 Dispatch Queue

1
2
dispatch_queue_t serialQueue = dispatch_queue_create("com.examlpe.gcd.serial",NULL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.examlpe.gcd.concurrent",DISPATCH_QUEUE_CONCURRENT);

上面是两种队列创建方式。

1
2
3
	dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr)

第一个参数是字符串类型,指定 Dispatch Queue 的名称,该名称会在 Xcode 和 Instruments 的调试器中作为 Dispatch Queue 的名称出现。一般用逆序全称域名,比如:”com.examlpe.gcd.serial”。
第二个参数是用来表示 Dispatch Queue 的类型,比如

  • 串行队列--NULL/DISPATCH_QUEUE_SERIAL
  • 并行队列--DISPATCH_QUEUE_CONCURRENT

通过 dispatch_queue_create 的方式常见 Dispatch Queue。还有一种是直接获取系统标准提供的 Dispatch Queue。

1
2
3
4
5
6
7
8
//获取主线程中执行的 Dispatch Queue 
dispatch_queue_t mainQueue = dispatch_get_main_queue()

//获取所有应用程序都能够使用的 Concurrent Dispatch Queue
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUP, 0)

对于主线程中的 Dispatch Queue,直接调用 dispatch_get_main_queue() 方法。

对于 Global Dispatch Queue 需要两个参数。

1
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

第一个参数表示优先级。它有4个执行优先级

1
2
3
4
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

第二个参数,从他的文档来看,是预留着将来用的,规定填写0。

小结

  • 创建一个 Serial Dispatch Queue 对应生成一个线程
  • 多个 Serial Dispatch Queue 执行任务是并行的
  • Main Dispatch Queue 是一个 Serial Dispatch Queue
  • Global Dispatch Queue 是一个 Concurrent Dispatch Queue
  • 对于 Global Dispatch Queue 的4种优先级处理内核只是做大致的判断不是精确判断。

dispatch_after

有时候可能会有延迟多少秒执行某段代码这样的需求,dispatch_after 就可以完成这样的任务,但是对于 dispatch_after 来说这个时间其实 ‘不是那么精确’,但是基本的需求还是能满足的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dispatch_time_t time1 = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

dispatch_time_t time2 = [self getDispatchWalltime:[NSDate date] delta:3];

dispatch_after(time2, dispatch_get_main_queue(), ^{

NSLog(@"三秒之后执行");
});

- (dispatch_time_t) getDispatchWalltime:(NSDate *) date delta:(int64_t) delta{

NSTimeInterval timeInterval;
double second, subsecond;
struct timespec time;
dispatch_time_t ansTime;

timeInterval = [date timeIntervalSince1970];
subsecond = modf(timeInterval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
ansTime = dispatch_walltime(&time, delta * NSEC_PER_SEC);

return ansTime;
}

第一个参数 dispatch_time_t 类型,可以用 dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC)
或者 [self getDispatchWalltime:[NSDate date] delta:3] 来生成。

第二个参数是追加到 Dispatch Queue 中去处理任务。
第三个参数是任务的 Block。

小结

  • dispatch_time 经常用于计算相对时间,dispatch_walltime 用于计算绝对时间

Dispatch Group

在 Dispatch Queue 中的多个任务处理完之后想执行结束处理,这种情况虽然你可以用一个 Serial Dispatch Queue 来完成这个需求,但是如果是在 Concurrent Dispatch Queue 处理起来就比较复杂了。但是比较好的解决方案应该是 Dispatch Group 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
NSLog(@"first");
});
dispatch_group_async(group, queue, ^{
NSLog(@"second");
});
dispatch_group_async(group, queue, ^{
NSLog(@"three");
});

dispatch_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"finish");
});

输出情况是 firstsecondthree 三个顺序是随机的,最后一个输出一定是 finish

参数就不做介绍了,比较明显。
个人认为对于串行队列的 Dispatch Group 好像是没有太大的意义的。

1
2
3
4
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)

long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

第二个参数表示的等待的时间(超时)。

  • DISPATCH_TIME_FOREVER 表示永久等待

这个 API 的返回值也就是 result 的值。

  • result != 0 Dispatch Group 中的任务未处理完。
  • result == 0 Dispatch Group 中的任务全部处理完。

小结

  • Dispatch Group 可以有效的处理一些结束处理 或者 ‘总结’处理。
  • dispatch_group_wait() 可以来检查队列中的任务是否完成
  • dispatch_group_wait() 会导致线程阻塞。

dispatch_sync

对应之前用过的 dispatch_async。

  • dispatch_sync 是同步的方式加入 Dispatch Queue
  • dispatch_async 是异步的方式加入 Dispatch Queue

同步的方式意思就是加入 Dispatch Queue 的任务处理完 dispatch_sync 才返回,允许执行下面的代码。
异步的方式意思就是任务加入 Dispatch Queue 后立即返回,并执行下面的代码。

总结

简单的介绍了 GCD 一些初级的 API,其实 GCD 中更多 API 来提供给我们使用像:dispatch_set_target_queue、dispatch_barrier_async、dispatch_apply 等等。还有待去研究研究。

以上属于个人见解,又不正确的地方欢迎指出。