iOS多线程:GCD
基本概念
什么是队列?
- 队列是执行任务的等待队列,用来存放任务。
- 它是一种特殊的线性表,采用FIFO(先进先出)的原则,新任务总是被插入到队列的末尾;
- 读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
- 队列负责管理多个任务,并拥有一个线程池,线程池中有一个或者多个线程。它按要求将每个任务调度到某个线程中执行。
串行队列 VS 并发队列
队列类型决定了任务的执行方式(并发或者串行)
串行队列:一个任务执行完毕后,继续执行下一个任务
并发队列:多个任务同时执行
同步 VS 异步
同步和异步决定是否了是否具备开启新线程的能力
同步:在当前线程执行,不具备开启新线程的能力
异步:在新的线程执行,具备开启新线程的能力
GCD
GCD的特点
- GCD是Apple开发的一个在线程池模式的基础上执行并发任务的多线程方案。
- 它可以自动利用更多的CPU内核进行并行运算,并自动管理线程的生命周期;我们只需要追加要执行的任务到适当的队列中,不需要编写任何线程管理代码。
- GCD的任务以
block
形式追加到队列中。 - GCD默认提供了主队列,所有主队列的任务都会放到主线程执行。
- GCD默认提供了全局并发队列,使用
dispatch_get_global_queue
方法来获取。
同步任务(dispatch_sync)和异步任务(dispatch_async):
dispatch_async
和dispatch_sync
的作用:- 将 任务(block)添加进指定的队列中
**async**
和**sync
决定调用该函数的线程是否需要阻塞,阻塞则等到block
执行完毕后返回,不阻塞则立即返回**。dispatch_sync(queue, block)
:把一个block
加入到指定的队列,并阻塞当前线程直到执行完block
,这个函数才返回;dispatch_async(queue, block)
:把一个block
加入到指定的队列中,不阻塞执行block
的线程,这个函数立刻返回。
队列和任务的执行组合
区别 并发队列 串行队列 主队列 同步 没有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务(死锁) 异步 有开启新线程\br并发执行任务 有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务 死锁
使用
sync
函数往当前串行队列中添加任务,会卡住当前的串行队列,产生死锁。示例1:
1
2
3
4
5
6
7
8int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_sync(dispatch_get_main_queue(), ^(void){
NSLog(@"这里死锁了");
});
}
return 0;
}分析:
在主线程上调用
dispatch_sync
,添加block
到主队列,并阻塞当前线程也就是主线程等待block
执行完成;主队列在主线程上调用,
block
在dispatch_asyn
函数返回后才能执行,而主线程现在处于被阻塞状态,因为block
执行后dispatch_sync
函数才能返回。所以无法执行block
。运行这段代码后,并不是卡在block
中无法返回,而是根本无法执行block
。示例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14(void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create(
"com.test.deadlock.queue",
DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"2");
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5")
}执行结果是1,2
原因是外层
dispatch_sync
执行完打印4才能返回,然后打印后追加到队列的任务——打印3;内层dispatch_sync
会阻塞当前的串行队列直到打印3之后才能返回去调用打印4,两者相互等待,造成死锁。
Dispatch Group
常用场景:分别异步执行两个任务,当两个任务都执行完毕后再回到主线程执行特定任务。
实现上分为两步:
- 添加耗时任务:
dispatch_async
+dispatch group
- 耗时任务完成后回到主线程执行特定任务:
dispatch_group_notify
/dispatch_group_wait
/dispatch_group_enter
+dispatch_group_leave
dispatch_group_notify
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14-(void)groupNotify {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t quque = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_async(group, queue, ^{
// 异步任务1
});
dispatch_group_async(group, queue, ^{
// 异步任务2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 任务1、2结束后调用
NSLog(@"group end");
});
}dispatch_group_wait
示例:1
2
3
4
5
6
7
8
9
10
11
12
13-(void)groupWait {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
//追加异步任务1
});
dispatch_group_async(group, queue, ^{
// 追加异步任务2
});
// 任务1、2执行完后,会往下继续执行
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}dispatch_group_enter
+dispatch_group_leave
dispatch_group_enter
:标志着一个任务追加到Group,执行一次,相当于Group中未执行任务数 + 1dispatch_group_leave
:标志着一个任务离开了Group,执行一次,相当于Group中未执行任务数 - 1当Group中未执行任务数为0时,才会使
dispatch_group_wait
解除阻塞,并执行追加到dispatch_group_notify
中的任务。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21-(void)groupEnterAndLeave {
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
dispatch_group_leave(group); // 每执行完一个任务调用一次
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务2
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
NSLog(@"group---end");
});
}
其他常用API
快速迭代方法:dispatch_apply
dispatch_apply
按照指定的次数将指定的任务追加到特定的队列中,并等待全部队列执行结束。- 在串行队列:就和for循环一样,按顺序同步执行。
- 在并发队列:异步执行,可在多个线程中同时执行多个任务。
无论是串行队列还是并发队列,
dispatch_apply
都会等待全部任务执行完毕。1
2
3
4
5
6
7
8
9
10
11// 快速迭代
-(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方法会等待全部任务执行完毕。GCD信号量:dispatch_semaphore
semaphore
的值表示线程最大并发数。- 常用来进行加锁操作;
- 也可以用来控制线程的最大并发数。
使用:
- 信号量>0,使信号量-1,并继续执行;
- 信号量<=0,新进来的线程就会进入休眠,直到
dispatch_semaphore_signal(semaphore)
使信号量>0时,被唤醒。
dispatch_semaphore_create
:创建一个Semaphore并初始化信号的总量dispatch_semaphore_wait
:使信号量 -1,信号总量小于0就会一直阻塞所在线程,否则就可以正常执行。dispatch_semaphore_signal
:发送一个信号,让信号总量加1dispatch_barrier_async
、dispatch_barrier_sync
异步栅栏
dispatch_barrier_async
:不会等待栅栏函数内任务执行完,就会执行后面主线程的任务。同步栅栏
dispatch_barrier_sync
:会等待栅栏函数内的任务执行完,再执行后面的主线程或者子线程任务。
优点:不会阻塞主线程
生效必要条件:使用自定义的并发队列,不能使用全局并发队列。使用全局并发队列时,效果同
dispatch_async
和dispatch_sync
1
2
3
4
5
6
7
8
9
10
11
12
13
14dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 异步任务1
});
dispatch_async(queue, ^{
// 异步任务2
});
// 任务1、2执行完成后执行栅栏任务
dispatch_barrier_async(queue, ^{
// 栅栏任务
});
dispatch_async(queue, ^{
// 异步任务3
});