GCD应用分析
1、dispatch_group_t 组
GCD线程同步
例如:同时进行多个网络请求(例:任务1、任务2),这几个任务互相不想干,但是等任务全部完成之后再执行接下来的任务,这种场景下,最常用的就是GCD 的group
1、dispatch_group_async
通过dispatch_group_async() 向组中添加任务1、任务2,然后通过dispatch_group_notify() 来执行任务1、任务2都结束之后接下的的任务。
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"task 1");
});
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"task 2");
});
//全部执行完毕
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"task done");
});
2、进组和出组
进组:dispatch_group_enter(),(输入)告诉 group 要添加任务到队列中了
出组:dispatch_group_leave(),(离开)告诉 group 一个任务执行完了,将其移出组
等待:dispatch_group_wait(), (等待) 阻塞当前线程,当leave 的次数与 enter 次数相等或超过时候才会继续往下执行。
完成:dispatch_group_notify(),(通知)leave 的次数与 enter 的次数相等时,会执行其中的任务。
因此dispatch_group_leave() 和 dispatch_group_wait() 要成对使用,
上例中的需求如果要求先完成任务1 之后再执行任务2,可修改如下:
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), globalQueue, ^{
//// sleep(1);
// NSLog(@"task 1");
// dispatch_group_leave(group);
// });
dispatch_async(globalQueue, ^{
sleep(2);
NSLog(@"task 1");
dispatch_group_leave(group);
});
//永远等待,也就是当任务1不出组(移出),当前线程就永远等待。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_enter(group);
dispatch_async(globalQueue, ^{
NSLog(@"task 2");
dispatch_group_leave(group);
});
// NSLog(@"task 2");
// dispatch_group_leave(group);
//全部执行完毕。非线程阻塞
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"task done");
});
NSLog(@"是否阻塞?");
dispatch_group_notify 函数不会阻塞线程,dispatch_group_wait 会阻塞线程。
dispatch_group_notify 函数可以直接添加任务,dispatch_group_wait 函数只是单纯的阻塞了线程继续向下执行,所以需要自己另行添加“总结性任务”
dispatch_group_notify 函数中的参数 queue 不论是否与 dispatch_group_async 是相同的queue队列,都会最后执行
2、dispatch_barrier_async
阻碍 同步 和 阻塞异步
2、dispatch_barrier_async 阻碍
这一系类API 只对通过dispatch_queue_create() 创建出来的 DISPATCH_QUEUE_CONCURRENT(并发)队列有效,如果是其他队列,比如全局或者不是通过 dispatch_queue_create()创建的 并发队列,那么dispatch_barrier_async / dispatch_barrier_sync 就会变成 dispatch_async / dispatch_sync 一样,失去特殊性,也就是说 dispatch_barrier_async 没有 “栅栏”的作用了。
dispatch_queue_t queue = dispatch_queue_create("com.liujilou.barrier", DISPATCH_QUEUE_CONCURRENT);
__block int a = 10;
dispatch_async(queue, ^{
NSLog(@"读取1 a=%d",a);
});
dispatch_async(queue, ^{
NSLog(@"读取2 a=%d",a);
});
dispatch_async(queue, ^{
NSLog(@"读取3 a=%d",a);
});
//
dispatch_barrier_async(queue, ^{
sleep(1);
NSLog(@"赋值 a=%d",a);
a = 20;
});
NSLog(@"主线程执行");
dispatch_async(queue, ^{
NSLog(@"读取4 a=%d",a);
});
dispatch_async(queue, ^{
NSLog(@"读取5 a=%d",a);
});
/*
2020-03-15 15:16:21.510753+0800 filedome[73509:2023353] 主线程执行
2020-03-15 15:16:21.510763+0800 filedome[73509:2023414] 读取1 a=10
2020-03-15 15:16:21.510781+0800 filedome[73509:2023415] 读取2 a=10
2020-03-15 15:16:21.510783+0800 filedome[73509:2023416] 读取3 a=10
2020-03-15 15:16:22.515361+0800 filedome[73509:2023416] 赋值 a=10
2020-03-15 15:16:22.515739+0800 filedome[73509:2023415] 读取5 a=20
2020-03-15 15:16:22.515736+0800 filedome[73509:2023416] 读取4 a=20
*/
通过打印结果分析可知,dispatch_barrier_async 之前添加到队列的任务会并发执行(读取1-3),并且在这些任务执行完成之后才会执行 dispatch_barrier_async 中的任务。同时 dispatch_barrier_async 会阻塞当前线程,需要直线完其中的任务z才能继续后面的这些,但是不会阻塞主线程,所以主线程能正常执行。执行完后面的任务继续并发执行。
图 dispatch_barrier_async.png
dispatch_barrier_sync 会阻塞主线程。
3、dispatch_suspend() 和 dispatch_resume()
挂起、恢复队列
dispatch_suspend(queue) 挂起指定的队列,(在一起不需要执行过度操作的地方,先挂起队列,然后再恢复队列);
dispatch_resume(queue) 恢复执行队列
对已经执行的处理没有影响。挂起后,追加到队列中,还没有执行的任务会被挂起直到恢复。恢复后处理继续执行。
dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
要成对使用,否者会报错。
4、dispatch_semaphore_create 信号量
信号量就是信号 >=1的时候,允许通过,否者不允许通过。是多线程的一种锁机制。
// 1、创建信号量为1
dispatch_semaphore_t semaphoer = dispatch_semaphore_create(1);
// 2、等待 加锁
// 第一个参数 信号量。信号量 >=1 的时候允许通过。并对其进行 减1操作。
// 等待 第二个参数等待时间,这里用一个未来的时间也就是一直等待。
dispatch_semaphore_wait(semaphoer, DISPATCH_TIME_FOREVER);
// 3、解锁
// 计数加1,相当于解锁。在任务执行完可以再次访问的时候进行加1 解锁
dispatch_semaphore_signal(sem);
// 这三个步骤必须都有,否者会崩溃。
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
// 计数 减 1
long result = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"task 1");
// 计数 加 1
dispatch_semaphore_signal(sem);
}else{
// 计数不为0的情况进行处理
NSLog(@"task 2");
}
dispatch_queue_t queue = dispatch_queue_create(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
for (int i=0; i<10000; i++) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
self.num++;
dispatch_semaphore_signal(sem);
NSLog(@"%d",self.num);
}
});
dispatch_async(queue, ^{
for (int i=0; i<10000; i++) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
self.num++;
dispatch_semaphore_signal(sem);
NSLog(@"%d",self.num);
}
});
// 这样就能打印到20000,如果不加锁中间可能会有同时访问拿到同样的值进行操作,这样得到的最终结果就不一定是20000,线程安全。
// 同时信号量也能控制开辟线程的数量,例如上面的代码我们可以设置信号量为2。可以开辟2个线程进行处理。
面试题分析
1、
dispatch_queue_t queue = dispatch_queue_create("liujilou", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
// NSLog(@"4");
});
NSLog(@"5");
死锁。1 5 2 正常打印执行。接下来是一个同步,同步的话会阻塞线程,去执行。但是这个同步块后面放的是一个 4,后面是3。4的执行需要等同步的块执行完,块里面有一个任务3 ,又往队里添加了一个任务3。block块完成就是执行3。由于是同步串行,不会开辟新的线程,先进先出原则所以 4需要等待块执行,块需执行3,但是3又需要等待4执行,就造成了死锁。
即便是注释掉4 任务仍然存在死锁。为什么内,因为同步函数 先加入到队列,任务3后加入到队列。任务3需要等待同步函数执行完才能执行,但是同步函数有需要任务3执行完才算执行完成。有形成了相互等待。
2、
dispatch_queue_t queue = dispatch_queue_create("liujilou", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
/*
2020-03-14 01:18:15.206466+0800 filedome[59905:1360188] 1
2020-03-14 01:18:15.206720+0800 filedome[59905:1360188] 5
2020-03-14 01:18:15.206845+0800 filedome[59905:1360270] 2
2020-03-14 01:18:15.206947+0800 filedome[59905:1360270] 3
2020-03-14 01:18:15.207040+0800 filedome[59905:1360270] 4
*/
代码编译从上到下顺序执行,先执行1,然后是异步函数不会阻塞线程同时会耗时所以先执行5,然后执行2。接下来是一个同步会阻塞线程,所以要先执行3,才能执行4.
3、
dispatch_queue_t queue = dispatch_queue_create("liujilou", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
/*
2020-03-14 01:19:52.246409+0800 filedome[59934:1361575] 1
2020-03-14 01:19:52.246576+0800 filedome[59934:1361575] 5
2020-03-14 01:19:52.246611+0800 filedome[59934:1361843] 2
2020-03-14 01:19:52.246703+0800 filedome[59934:1361843] 4
2020-03-14 01:19:52.246717+0800 filedome[59934:1361634] 3
*/
代码编译从上到下顺序执行,先执行1,然后是异步函数不会阻塞线程同时会耗时所以先执行5,然后执行2。接下来又是一个异步 同理先执行了4再执行3.
4、
dispatch_queue_t queue = dispatch_queue_create("com.liujilou.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
/*
2020-03-14 01:27:51.772658+0800 filedome[60051:1366298] 3
2020-03-14 01:27:51.772671+0800 filedome[60051:1366397] 1
2020-03-14 01:27:51.772676+0800 filedome[60051:1366395] 2
2020-03-14 01:27:51.772784+0800 filedome[60051:1366298] 0
2020-03-14 01:27:51.772906+0800 filedome[60051:1366396] 9
2020-03-14 01:27:51.772889+0800 filedome[60051:1366395] 7
2020-03-14 01:27:51.772920+0800 filedome[60051:1366397] 8
*/
这个的结果会有多种,但是有几个值的先后顺序是固定的。3是同步所以后面的0 需要等待3执行完才执行,后面的是三个异步需要等0执行完再执行。
1 2 3 这个三个顺序不一定, 3 0 顺序固定。 7 8 9 顺序不一定,但是一定在0之后。多以只要满足这些条件的均正确。