多线程:GCD
使用 GCD 的时候如何让线程同步,也有多种方法
- dispatch_group
- dispatch_barrier
- dispatch_semaphore
快速迭代方法:dispatch_apply
1
2
3
4
5
6
7
// 阻塞当前线程,多次执行 block 代码块,类似快速遍历的 for 循环,index 无序。
// 只有结合并行队列时才有意义,如果同步队列就体现不出多线程的性能优势
dispatch_apply(1000, queue, ^(size_t index){
NSLog(@"第 %zu 次执行 === %@",index, [NSThread currentThread]);
});
NSLog(@"Done ===");
dispatch_barrier_async 栅栏
当任务需要异步进行,但是这些任务需要分成两组来执行,第一组完成之后才能进行第二组的操作。这时候就用了到 GCD 的栅栏方法 dispatch_barrier_async。
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
- (IBAction)barrierGCD:(id)sender {
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 异步并发执行
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步1 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步2 %@",[NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"------------barrier------------%@", [NSThread currentThread]);
NSLog(@"******* 并发异步执行,但是 3、4 一定在 1、2 后面 *********");
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步3 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步4 %@",[NSThread currentThread]);
}
});
}
上面代码开启多条线程,所有任务都是异步并发进行。但是 1、2 完成之后,才会进行 3、4 的操作。
dispatch_group_t 队列组示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)testGroup {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"有一个耗时操作完成!");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"有一个耗时操作完成!");
});
// 不阻塞线程,group 中的所有其他任务都执行完后才会执行 dispatch_group_notify 的任务
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"队列组中的耗时操作都完成了!");
});
// 阻塞线程,等待 group 中的操作(不包含 dispatch_group_notify)都执行完后解除阻塞
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@" waiting... ");
NSLog(@" waiting... ");
NSLog(@" waiting... ");
}
dispatch_semaphore 相关的3个函数
1
2
3
4
5
6
7
8
9
dispatch_time 的声明如下:
// 以下方法返回值表示 when 向后延时 delta(单位是纳秒)时间
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
例如:
// timeout 表示当前时间向后延时一秒。
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);
1
2
3
4
5
6
7
8
9
10
// 创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_t dispatch_semaphore_create(long value);
// 接收一个信号和时间值(一般为 DISPATCH_TIME_FOREVER )
// 若信号量等于 0,则会阻塞当前线程,直到信号量大于 0 或者等待时间 timeout 后程序才会继续往下执行
// 若信号量大于 0,则会使信号量减 1 并返回,程序继续住下执行
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
// 提高信号量, 使信号量加 1 并返回
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为线程加锁
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 20; i++) {
dispatch_async(queue, ^{
// 相当于加锁,若执行这句代码之前的信号量大于 0,则这句代码返回值是 0;否则返回值不为 0
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"i = %d semaphore = %@", i, semaphore);
// 相当于解锁,某些时候少了这句代码会报错:EXC_BAD_INSTRUCTION
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_enter 和 dispatch_group_leave
当 group 中的 异步并发 任务内部又嵌套了 异步并发 任务时,内部嵌套的异步并发任务在没有执行完时就会开始执行 dispatch_group_notify。这时可以用以上两个方法解决,真正达到时使 dispatch_group_notify 中的任务在 group 中最后执行
注意
- dispatch_group_enter :未完成的任务计数 + 1,往 group 中添加一个任务
- dispatch_group_leave :未完成的任务计数 - 1,从 group 中移除一个任务
- 只要未完成的任务计数 > 0,dispatch_group_notify 就不会执行
- 两者必须成对出现,配合使用,否则会崩溃。
示例代码:
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
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t concurrentQ = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 多个 dispatch_group_enter 可以都写在开头
dispatch_group_enter(group);
// dispatch_group_enter(group);
// dispatch_group_enter(group);
dispatch_group_async(group, concurrentQ, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"1--over");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, concurrentQ, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"2--over");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, concurrentQ, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3--over");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, concurrentQ, ^{
NSLog(@"其他全部结束");
});
NSLog(@" waiting... ");
NSLog(@" waiting... ");
NSLog(@" waiting... ");
dispatch_suspend & dispatch_resume
- 队列挂起和恢复,甚至可以在不同的线程对这个队列进行挂起和恢复,因为 GCD 是对队列的管理
- 对已经在执行中的任务没有影响,队列中尚未执行的停止执行
- 恢复指定的 queue 队列,使尚未执行的任务继续执行
- 当队列没有挂起时调用 dispatch_resume 方法会崩溃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"%i",i);
if (i == 50) {
NSLog(@"-----------------------------------");
dispatch_suspend(queue);
sleep(3);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_resume(queue);
});
}
}
});
Dispatch Source 调度源
- Dispatch Source 能通过合并事件的方式确保在高负载下正常工作
- DISPATCH_SOURCE_TYPE_DATA_ADD 在很短的时间间隔内,一个事件的的触发频率很高,那么 Dispatch Source 会将这些响应以 ADD 的方式进行累积,然后等系统空闲时一次性处理,如果触发频率比较零散,那么 Dispatch Source 会将这些事件分别响应。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 创建 source,以 ADD 的方式进行累加,而 DISPATCH_SOURCE_TYPE_DATA_OR 是对结果进行二进制或运算
// 此处定义的句柄在主线程执行
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
__block NSUInteger totalComplete = 0;
// 事件触发后执行的句柄
dispatch_source_set_event_handler(source, ^{
// 这个数据的值在每次响应事件执行后会被重置,所以 totalComplete 的值是最终累积的值。
NSUInteger value = dispatch_source_get_data(source);
totalComplete += value;
NSLog(@"待处理的数据量:%lu", dispatch_source_get_data(source));
NSLog(@"进度:%@", @((CGFloat)totalComplete/100));
dispatch_source_cancel(source);
});
// 源取消时的回调
dispatch_source_set_cancel_handler(source, ^{
NSLog(@"## 如果source 被取消,则执行这个函数 ##");
});
// 调用 dispatch_resume(source) 方法恢复调度源后,就会执行这个 block,
// 并不需要调用 dispatch_source_merge_data(source, 1) 才执行
dispatch_source_set_registration_handler(source, ^{
NSLog(@"我是注册收到的回调");
});
// source 创建完默认是挂起的,需要手动调用方法恢复。
dispatch_resume(source);
// 如果调用完 dispatch_resume(source); 后再直接调用 dispatch_source_merge_data(source, 1);
// 则可能会不触发事件 block,莫名其妙? 但是只要在 block 中调用一次 dispatch_source_get_data(source); 就会正常。。。
// 所以最好在 dispatch_source_set_event_handler 的 block 中调用一次 dispatch_source_get_data(source)
// dispatch_source_merge_data(source, 1);
// 简单来个串行队列
dispatch_queue_t queue = dispatch_queue_create(nil, 0);
// 恢复源后,就可以通过 dispatch_source_merge_data 向调度源发送事件
for (NSUInteger index = 0; index < 100; index++) {
dispatch_async(queue, ^{
// 可在任意线程触发事件,这里 i 必须大于 0,否则触发不了事件
dispatch_source_merge_data(source, 1);
NSLog(@"线程号:%@~~~~~~~~~~~~i = %ld", [NSThread currentThread], index);
// 睡眠 0.02秒,当间隔的时间足够长,则每次都会触发句柄
usleep(20000);
});
}
// 这里仅仅是暂停 source 去调用前面设置的句柄,而 for 循环并不会暂停
// dispatch_suspend(source);
// 要想真正暂停 source,需要让 Dispatch Source 与 Dispatch Queue 同时实现暂停和恢复
// dispatch_suspend(queue);
使用 dispatch_source_set_timer 定时器
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
when:从什么时间开始,一般直接传 DISPATCH_TIME_NOW 表示从现在开始
delta:单位是纳秒! 表示具体的时间长度,可以写成这种形式: 3 * NSEC_PER_SEC
dispatch_time(dispatch_time_t when, int64_t delta)
timespec 是一个结构体, 创建的是一个绝对的时间点,比如 2016年10月10日8点30分30秒。
如果传 NUll 表示自动获取当前时区的当前时间作为开始时刻
dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
举例,当前时间往后 5 秒:
dispatch_time_t time = dispatch_walltime(NULL, 5 * NSEC_PER_SEC);
以上两个函数的区别:
第一个函数,如果设备进入休眠,系统时钟也休眠,则 dispatch_time 函数也会休眠;设备唤醒后函数也同时被唤醒
第二个函数则不受系统时钟是否休眠的影响,都能准备计时。
source 调度源
start 定时器开始时间。需要用 dispatch_time 或 dispatch_walltime 函数来创建。
interval 定时器的间隔时间
leeway 定时器触发的精准程度
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway)
// =======================================================================
// 倒计时时间
__block int timeout = 3;
// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建 timer
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置 1s 触发一次,0s 的误差
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0*NSEC_PER_SEC, 0);
// 触发的事件
dispatch_source_set_event_handler(_timer, ^{
if (timeout <= 0) {
// 取消dispatch源
dispatch_source_cancel(_timer);
} else {
timeout--;
dispatch_async(dispatch_get_main_queue(), ^{
// 更新主界面的操作
NSLog(@"~~~~~~~~~~~~~~~~%d", timeout);
});
}
});
// 恢复定时器调度源。如果调度源类型是 DISPATCH_SOURCE_TYPE_TIMER,则恢复后会自动触发句柄事件
dispatch_resume(_timer);
本文由作者按照 CC BY 4.0 进行授权