发布于 

iOS 多线程 - GCD

GCD(Grand Central Dispatch)是苹果提出的一套多线程解决方案,它拥有系统级的线程管理机制,开发者不需要关注线程的生命周期管理,只需要关注要执行的任务。GCD 可用于多核的并行运算,会自动利用更多的 CPU 内核。

多线程的基础概念

先看下 iOS 多线程编程的一些概念。

进程 & 线程

  • 进程(process):iOS 中指一个正在运行的 App。每个进程可以拥有独立的虚拟内存空间和系统资源,至少包含一个主线程和任意的辅助线程。当一个进程中的主线程退出时,这个进程也会结束。
  • 线程(thread):指一个独立的代码执行路径。iOS 中线程的底层实现是基于 pthreads。
  • 任务(task):指一个要执行的任务。如执行一段代码。

串行 & 并发

  • 串行:指一次只能执行一个任务,必须等一个任务执行完成后,才会执行下一个任务。
  • 并发:指允许多个任务同时执行。

两者的区别在于,在于同时允许执行的任务数量。

同步 & 异步

  • 同步:等待在执行的代码完成后,再执行接下来的代码。
  • 异步:在代码调用后,立刻返回,不会等待代码执行的结果。

两者的区别在于,是否等待代码执行完成,即是否阻塞当前线程。

队列

iOS 中有两种队列:

  • 串行队列:一次只执行一个任务。
  • 并发队列:允许多个任务同时执行。

iOS 系统通过队列来进行任务的调度,系统根据调度任务的需要和系统当前负载的情况动态创建和销毁线程。

GCD 用法

记录 GCD 的一些常规用法。

dispatch_queue_t

GCD 提供了 4 种全局队列和 1 个主队列。主队列是串行队列,其余是并行队列,优先级越高,越有限执行。

  • **dispatch_get_main_queue()**:主队列,会在主线程执行,串行。

  • **dispatch_get_global_queue(long identifier, unsigned long flags)**:全局队列,flags 是苹果预留字段,传入非 0 可能会导致返回 nil,identifier 有四种:

    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
  • DISPATCH_QUEUE_PRIORITY_HIGH:适用场景是需要及时得到处理结果。

  • DISPATCH_QUEUE_PRIORITY_DEFAULT:默认优先级。

  • DISPATCH_QUEUE_PRIORITY_LOW:适用于需要长时间运行的不紧急的任务。

  • DISPATCH_QUEUE_PRIORITY_BACKGROUND:适用于预加载或不需要用户交互和对时间不敏感的任务。

dispatch_queue_create

普通用法

创建自定义队列:

1
2
3
4
5
6
// 创建串行队列(NULL 默认为串行队列)
// 优先级默认为 DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_queue_t serialQueue = dispatch_queue_create("com.jonyfang.serial", DISPATCH_QUEUE_SERIAL);
// 创建并行队列
// 优先级默认为 DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jonyfang.concurrent", DISPATCH_QUEUE_CONCURRENT);

自定义小优先级

也可以通过 dispatch_queue_attr_make_with_qos_class 创建带有优先级的 dispatch_queue_attr_t

1
2
3
dispatch_queue_attr_t
dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t _Nullable attr,
dispatch_qos_class_t qos_class, int relative_priority);
  • attr:传入 DISPATCH_QUEUE_SERIAL、NULL 或 DISPATCH_QUEUE_CONCURRENT,表示串行或并行。
  • qos_class:传入 qos_class 枚举,表示优先级级别。
  • relative_priority:相对于 qos_class 的相对优先级,qos_class 用于区分大的优先级级别,relative_priority 表示大级别下的小级别。relative_priority 必须大于 QOS_MIN_RELATIVE_PRIORITY(-15) 小于 0,否则将返回 NULL。传入 NULL 只会创建出串行队列。

用法示例:

1
2
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_DEFAULT, -2);
dispatch_queue_t customQueue = dispatch_queue_create("com.jonyfang.custom", attr);

dispatch_set_target_queue

指定队列优先级

自带的 dispatch_get_global_queue 可以指定优先级;而通过 dispatch_queue_create 生成的 queue 不管是 Serial 还是 Concurrent,它们的执行优先级都默认为 DISPATCH_QUEUE_PRIORITY_DEFAULT。如果我们想改变自定义 queue 的优先级,可以通过 dispatch_set_target_queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dispatch_queue_t customQueue = dispatch_queue_create("com.jony.serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

// 第一个参数为要设置优先级的 queue
// 第二个参数是参照物
// 将 customQueue 的优先级和 globalQueue 的优先级设置为一样。
dispatch_set_target_queue(customQueue, globalQueue);

dispatch_async(customQueue, ^{
NSLog(@"CUSTOM-LOW-QUEUE");
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"GLOBAL-DEFAULT-QUEUE");
});

打印结果:
2016-03-09 11:08:49.388 Demo[1408:539481] GLOBAL-DEFAULT-QUEUE
2016-03-09 11:08:49.388 Demo[1408:539185] CUSTOM-LOW-QUEUE

改变队列执行方式

dispatch_set_target_queue 除了可以指定队列优先级,还可以改变多个队列的执行顺序。当我们想让不同的队列,同步执行,而不再并行执行。可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    //1.创建目标队列
dispatch_queue_t targetQueue = dispatch_queue_create("com.jony.target", DISPATCH_QUEUE_SERIAL);

//2.创建 3 个串行队列
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);

//3.将 3 个串行队列分别添加到目标队列
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);


dispatch_async(queue1, ^{
NSLog(@"queue.1 in");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"queue.1 out");
});

dispatch_async(queue2, ^{
NSLog(@"queue.2 in");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"queue.2 out");
});
dispatch_async(queue3, ^{
NSLog(@"queue.3 in");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"queue.3 out");
});

//打印结果:
2016-03-09 11:37:05.638 Demo[1456:648304] queue.1 in
2016-03-09 11:37:08.643 Demo[1456:648304] queue.1 out
2016-03-09 11:37:08.644 Demo[1456:648304] queue.2 in
2016-03-09 11:37:10.649 Demo[1456:648304] queue.2 out
2016-03-09 11:37:10.649 Demo[1456:648304] queue.3 in
2016-03-09 11:37:11.652 Demo[1456:648304] queue.3 out

aysnc & sync

1
2
3
4
// 异步调用
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 同步调用
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • queue:block 执行所在的队列
  • block:执行的 block

异步调用会立刻返回;但同步调用会阻塞当前线程,等待 block 执行完毕后才继续。

死锁问题:

1
2
3
4
5
6
7
dispatch_queue_t serialQueue = dispatch_queue_create("com.jony.serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
///发生死锁
NSLog(@"Here I am.");
});
});

如上代码会发生死锁,原因是:dispatch_async()dispatch_sync() 都被加入了串行队列 serialQueuedispatch_sync() 想要执行,就必须等待 dispatch_async() 的 Block 执行完毕。而 Block 想要执行完毕,必须先执行完 dispatch_sync()。这样就造成了互相持续等待,发生了死锁。

dispatch_once

dispatch_once 能保证任务只被执行一次,即使多线程调用也是安全的。常用在单例创建、Method Swizzeling。

示例代码:

1
2
3
4
5
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 进行单例创建
// 或 Method Swizzling
});

单例示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface FFPerson : NSObject
+ (instancetype)shareInstance;
@end

@implementation FFPerson

+ (instancetype)shareInstance {
static FFPerson *_person = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_person = [[FFPerson alloc] init];
});
return _person;
}

@end

dispatch_after

dispatch_after 指将一个任务(Block 内代码)延迟一段时间后,再追加到一个队列中执行。

1
2
3
4
5
// 延迟 1 秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// 延迟提交的 block
});

dispatch_apply

dispatch_apply 是同步的调用,不会立刻返回,等待执行完毕后再返回。作用是将一个任务追加到队列中多次执行。该队列串行或并行有队列本身决定。

如遍历一个数组内全部元素:

1
2
3
4
// 遍历 5 次
dispatch_apply(5, dispatch_get_main_queue(), ^(size_t index) {
// 遍历执行的代码
});

dispatch_barrier_async

dispatch_barrier_async 自己理解为是一个分隔板,dispatch_barrier_async 需要等待 dispatch_barrier_async 之前的任务全部执行完毕才执行,同时 dispatch_barrier_async 之后的任务也会进行等待 dispatch_barrier_async 执行完后才执行。

dispatch_barrier_asyncdispatch_barrier_sync 的区别:

  • dispatch_barrier_async 体现了异步的特点,不做任何等待直接返回,只保证 block 块开始执行的顺序是按照我们想要的顺序。
  • dispatch_barrier_sync 不光能按我们想要的顺序执行 Block 内任务,dispatch_barrier_sync 还会等待前面的 Block 执行完成,才会继续后面的代码执行。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
NSLog(@"BEGIN");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任务 1
for (int i = 0; i < 2; i++) {
NSLog(@"task1 来自线程:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
// 任务 2
for (int i = 0; i < 2 ; i++) {
NSLog(@"task2 来自线程:%@",[NSThread currentThread]);
}
});
NSLog(@"task1、task2 后,barrier 前");
dispatch_barrier_async(queue, ^{
// barrier
for (int i = 0; i < 1 ; i++) {
NSLog(@"barrier 来自线程:%@",[NSThread currentThread]);
}
});

dispatch_async(queue, ^{
// 任务 3
for (int i = 0; i < 1 ; i++) {
NSLog(@"task3 来自线程:%@",[NSThread currentThread]);
}
});
NSLog(@"END");

// 打印结果:
2016-03-09 13:47:17.656139+0800 Demo[4912:51826] BEGIN
2016-03-09 13:47:17.656348+0800 Demo[4912:51826] task1、task2 后,barrier 前
2016-03-09 13:47:17.656463+0800 Demo[4912:51882] task1 来自线程:<NSThread: 0x60000047f740>{number = 3, name = (null)}
2016-03-09 13:47:17.656470+0800 Demo[4912:51884] task2 来自线程:<NSThread: 0x60000047ed00>{number = 4, name = (null)}
2016-03-09 13:47:17.656599+0800 Demo[4912:51826] END
2016-03-09 13:47:17.656654+0800 Demo[4912:51884] task2 来自线程:<NSThread: 0x60000047ed00>{number = 4, name = (null)}
2016-03-09 13:47:17.656654+0800 Demo[4912:51882] task1 来自线程:<NSThread: 0x60000047f740>{number = 3, name = (null)}
2016-03-09 13:47:17.657139+0800 Demo[4912:51882] barrier 来自线程:<NSThread: 0x60000047f740>{number = 3, name = (null)}
2016-03-09 13:47:17.657313+0800 Demo[4912:51882] task3 来自线程:<NSThread: 0x60000047f740>{number = 3, name = (null)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
NSLog(@"BEGIN");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//任务1
for (int i = 0; i < 2; i++) {
NSLog(@"task1 来自线程:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
//任务2
for (int i = 0; i < 2 ; i++) {
NSLog(@"task2 来自线程:%@",[NSThread currentThread]);
}
});

NSLog(@"task1/task2 后,barrier 前:%@",[NSThread currentThread]);
dispatch_barrier_sync(queue, ^{
//珊栏
for (int i = 0; i < 1 ; i++) {
NSLog(@"barrier 来自线程:%@",[NSThread currentThread]);
}
});

dispatch_async(queue, ^{
//任务3
for (int i = 0; i < 1 ; i++) {
NSLog(@"task3 来自线程:%@",[NSThread currentThread]);
}
});
NSLog(@"END");

// 打印结果:
2016-03-09 13:54:09.045223+0800 Demo[5014:57352] BEGIN
2016-03-09 13:54:09.045461+0800 Demo[5014:57352] task1/task2 后,barrier 前:<NSThread: 0x600000072f00>{number = 1, name = main}
2016-03-09 13:54:09.045486+0800 Demo[5014:57400] task2 来自线程:<NSThread: 0x60000026dd80>{number = 4, name = (null)}
2016-03-09 13:54:09.045496+0800 Demo[5014:57404] task1 来自线程:<NSThread: 0x600000260b80>{number = 3, name = (null)}
2016-03-09 13:54:09.045758+0800 Demo[5014:57400] task2 来自线程:<NSThread: 0x60000026dd80>{number = 4, name = (null)}
2016-03-09 13:54:09.045830+0800 Demo[5014:57404] task1 来自线程:<NSThread: 0x600000260b80>{number = 3, name = (null)}
2016-03-09 13:54:09.045991+0800 Demo[5014:57352] barrier 来自线程:<NSThread: 0x600000072f00>{number = 1, name = main}
2016-03-09 13:54:09.046099+0800 Demo[5014:57352] END
2016-03-09 13:54:09.046124+0800 Demo[5014:57404] task3 来自线程:<NSThread: 0x600000260b80>{number = 3, name = (null)}

dispatch_group_t

diapatch_group_t 可以用来监控不同队列中的不同任务。当 group 内所有任务完成(Block 执行完毕),GCD 提供了两种告知方式:

  • dispatch_group_wait:会阻塞当前线程(所以不可以放到主线程调用),等待所有任务完成或超时。
  • dispatch_group_notify:异步执行 Block 内任务,不阻塞。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_queue_t queue = dispatch_queue_create("com.jony.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
NSLog(@"1");
sleep(1);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2");
sleep(2);
});

// 阻塞
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t queue = dispatch_queue_create("com.jony.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
NSLog(@"1");
sleep(1);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2");
sleep(2);
});
// 异步
dispatch_group_notify(group, queue, ^{
NSLog(@"所有任务完成");
});

dispatch_time_t

GCD 也可以用来做定时器。

单次执行:

1
2
3
4
5
// 执行一次(利用 dispatch_after)
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
dispatch_after(timer, dispatch_get_main_queue(), ^(void){
// 执行事件
});

每隔一秒重复执行:

1
2
3
4
5
6
7
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 1 * NSEC_PER_SEC, 0); // 每秒执行
dispatch_source_set_event_handler(timer, ^{
// 执行事件
});
dispatch_resume(timer);

dispatch_semaphore

Dispatch Semaphore 是持有计数的信号,计数为 0 时等待,计数 >= 1 时放行。

dispatch_semaphore 的基本用法:

  • dispatch_semaphore_create 可以生成信号量,参数 value 是信号量计数的初始值;
  • dispatch_semaphore_wait 会让信号量值 -1,当信号量值为 0 时进入等待(直到超时),否则正常执行;
  • dispatch_semaphore_signal 会让信号量值 +1,如果有通过 dispatch_semaphore_wait 函数等待 Dispatch Semaphore 的计数值增加的线程,会由系统唤醒最先等待的线程执行。
1
2
3
4
5
6
// 在 init 等函数初始化
_lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
// 修改Array或字典等数据的信息

dispatch_semaphore_signal(_lock);

常会遇到的链式请求,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 链式请求,网络请求需要串行执行,第一个请求成功后再开始第二个请求
- (void)requestConfig {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *list = @[@"1",@"2",@"3"];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self fetchConfigWithCompletion:^(NSDictionary *dict) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}];
});
}

- (void)fetchConfigWithCompletion:(void(^)(NSDictionary *dict))completion {
// 进行网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模拟网络请求
sleep(2);
!completion ? nil : completion(nil);
});
}

GCD vc NSOperation

GCD 由苹果官方提供的一个用于多核编程的解决方案,基于 C 开发。主要用于让应用程序支持多核处理,提高应用性能。NSoperation 是基于 GCD 封装的,相比 GCD 使用更加灵活,功能更强大。

  • GCD:轻量,以 FIFO 顺序执行任务。使用 GCD 时,我们不用关心任务的调度,交由系统处理。但如果我们想要给任务之间添加依赖关系、取消或暂停一个正在执行的任务,GCD 不能很好地支持。
  • NSOperation:相比 GCD,NSOperation 会增加一些额外的开销,但具有更高的灵活性和更多的功能。我们可以基于 Operation 给任务之间添加依赖、取消、暂停或恢复一个在执行的任务。

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @JonyFang 创建,使用 Stellar 作为主题,您可以在 GitHub 找到本站源码。