看这篇文章前,如果对队列和任务的理解比较混乱,可以参考 概念篇–队列与任务
1. GCD的使用步骤
GCD 的使用步骤只有两步:
-
创建一个队列(串行队列或并发队列);
-
将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
1.1 队列的创建方法 / 获取方法
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("a.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("b.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
复制代码
1.2 任务的创建方法
// 同步执行任务 创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务 创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
复制代码
2. 死锁的情况
2.1 在『异步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
复制代码
执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了『串行队列』,最终造成了串行队列所在的线程(子线程)死锁问题。
2.2 主队列造成死锁的原因同上. 所以,这也进一步说明了主队列其实并不特殊。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@" gcd 前");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@" gcd 内");
});
NSLog(@" gcd 后");
}
复制代码
2.3 在『同步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{ // 同步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
复制代码
3. GCD简单使用
3.1 处理耗时操作
由线程完成耗时操作,需要回到主线程,刷新UI
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
复制代码
3.2 延迟执行任务 dispatch_after
dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。
/**
* 延时执行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
复制代码
4. GCD高级应用
4.1 栅栏函数
dispatch_barrier_async的简单使用
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
复制代码
栅栏函数有 2 种,同步dispatch_barrier_sync
和异步dispatch_barrier_async
,下面来打印比较下它们的异同
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_async(dispatch_queue_create("mmmmmm", DISPATCH_QUEUE_CONCURRENT), ^{
[self logBarrierOrder];
});
}
- (void)logBarrierOrder {
NSLog(@"%@ >>>>> start ", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test.barrier.queue", DISPATCH_QUEUE_CONCURRENT);
//异步函数 无序执行
NSLog(@"%@ >>>>> barrier 前面 A ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(5);
NSLog(@"%@ >>>>> 任务 1 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 前面 B ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"%@ >>>>> 任务 2 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 前面 C ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@ >>>>> 任务 3 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 前面 D ", [NSThread currentThread]);
dispatch_barrier_sync(queue, ^{
sleep(7);
NSLog(@"%@ ++++++ barrier ++++++ ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 后面 E ", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"%@ >>>>> 任务 4 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 后面 F ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(10);
NSLog(@"%@ >>>>> 任务 5 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 后面 G ", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"%@ >>>>> 任务 6 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> end ", [NSThread currentThread]);
}
复制代码
dispatch_barrier_sync执行结果
>>>>> start
>>>>> barrier 前面 A
>>>>> barrier 前面 B
>>>>> barrier 前面 C
>>>>> barrier 前面 D
>>>>> 任务 3
>>>>> 任务 2
>>>>> 任务 1
++++++ barrier ++++++
>>>>> barrier 后面 E
>>>>> barrier 后面 F
>>>>> 任务 4
>>>>> barrier 后面 G
>>>>> end
>>>>> 任务 6
>>>>> 任务 5
dispatch_barrier_async执行结果
>>>>> start
>>>>> barrier 前面 A
>>>>> barrier 前面 B
>>>>> barrier 前面 C
>>>>> barrier 前面 D
>>>>> barrier 后面 E
>>>>> barrier 后面 F
>>>>> barrier 后面 G
>>>>> end
>>>>> 任务 3
>>>>> 任务 2
>>>>> 任务 1
++++++ barrier ++++++
>>>>> 任务 4
>>>>> 任务 6
>>>>> 任务 5
- 共同点
- 先等任务1,2,3执行完,再执行barrier,执行完barrier,最后执行任务 4,5,6
- 不同点
- dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续执行写在barrier后面的任务(E、F、G),然后执行后面的任务(4,5,6)
- dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(E、F、G)插入到queue
栅栏函数注意点:
- 尽量使用自定义的并发队列,使用全局队列起不到栅栏函数的作用.使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)
- 栅栏函数只能控制同一并发队列
- dispatch_barrier_async和dispatch_barrier_sync的异同:都可以阻止(此线程中)队列的执行,但是dispatch_barrier_sync也阻止了线程的执行
- dispatch_barrier_sync是在主线程中执行,dispatch_barrier_async是在子线程中执行。
- 在串行队列中,dispatch_barrier_sync和dispatch_barrier_async功能一样。dispatch_async会创建新的线程,如果是串行队列,那么只开启一个线程, 任务按照加入的顺序执行。
4.2 一次性代码(只执行一次): dispatch_once
/**
* 一次性代码(只执行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
}
复制代码
4.3 GCD 快速迭代方法:dispatch_apply
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。
/**
* 快速迭代方法 dispatch_apply
* 遍历 0~5 这 6 个数字
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
复制代码
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply—end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。
4.4 组队列
dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
复制代码
dispatch_group_enter、dispatch_group_leave
- dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
- dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
- 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
/**
* 队列组 dispatch_group_enter、dispatch_group_leave
*/
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
复制代码
dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
/**
* 队列组 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
复制代码
4.5 GCD 信号量:dispatch_semaphore
-
dispatch_semaphore相关的3个函数
-
// 创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_t dispatch_semaphore_create(long value);
-
/* 等待降低信号量,接收一个信号和时间值(多为DISPATCH_TIME_FOREVER)
若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;
若信号量大于0,则会使信号量减1并返回,程序继续住下执行
*/
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
-
// 提高信号量, 使信号量加1并返回
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
-
-
发送信号(signal)与等待信号(wait)往往是成对出现的。
① 通过dispatch_semaphore控制异步方法一个执行完再执行另一个
-(void)dispatchSignal{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
NSLog(@"run task 1");
sleep(2);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//任务2
dispatch_async(quene, ^{
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//任务3
dispatch_async(quene, ^{
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
NSLog(@"-------->end");
}
复制代码
执行结果
run task 1
complete task 1
run task 2
complete task 2
run task 3
complete task 3
——–>end
② 通过dispatch_semaphore_create的值控制同一时间执行的线程数
/*
dispatch_semaphore_create的value表示,最多几个资源可访问
如果信号值设为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
如果设定的信号值为3,在这个方法里就是不限制线程执行了,因为一共才只有3个线程。
*/
-(void)dispatchSignal{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(2);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}
复制代码
执行结果
run task 1
run task 2
complete task 2
complete task 1
run task 3
complete task 3