iOS GCD
GCD简介
GCD 是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快。通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。这样通过GCD来管理线程,从而解决线程被创建的问题。
GCD基本概念
任务和队列
名词 | 说明 |
---|---|
线程 | 程序执行任务的最小调度单元 |
任务 | 一段代码:GCD中,block中需要执行的code |
队列 | 存放任务的数组,FIFO(先进先出)的原则 |
异步、同步,并行和串行
名词 | 说明 |
---|---|
异步(async) | 具备开新线程的能力 |
同步(sync) | 不具备开新线程的能力 |
并行 | 任务可以并发执行 |
串行 | 任务按顺序执行 |
说明:使用GCD开启多线程执行多个任务时,需具备两个条件:
1、能开启新线程
2、任务可以同时执行
即:"异步"+"并行"
GCD几种组合
- | 并行队列 | 串行队列 | 主队列 |
---|---|---|---|
异步 | 开启新线程,任务同时执行(1) | 开启新线程,任务顺序执行(2) | 不开新线程,任务顺序执行(5) |
同步 | 不开启新线程,任务顺序执行(3) | 不开启新线程,任务顺序执行(4) | 主线程中:死锁,子线程中:不开启新线程,任务顺序执行(6) |
详细说明:
(1)异步使得队列开启了新的线程,并行队列让任务可以同时执行(常用)
(2)虽然开启了新线程,但是队列调度方式是串行的,因此任务只能顺序执行
(3)同步意味着不能开启新线程,虽然是并行队列,但线程只有一个,因此任务只能顺序执行
(4)不能开新线程,任务队列是串行,任务顺序执行
总结:正如上面说到,使用GCD完成多线程多任务时,需要具备两个能力:开启新线程的能力,任务可以同时执行的能力,即”异步”+”并行”。两者缺一不可,缺少任何一个,队列里的任务都是顺序执行。
接下来说明一下主队列:主队列其实是一个串行队列
(5)主队列里的任务都是在主线程中完成的,即使使用异步(async),也不会开启新线程,并且主队列是一个串行队列,任务顺序执行。
(6)在主线程中出现死锁
,是因为任务被加到主队列中,想要被执行block中的代码必须等到主线程上的任务都执行完毕,但是,因为是同步任务,想要主线程上的任务执行完毕,势必需要执行任务中的block中的代码,因此两者相互等待,出现死锁
;但是如果在子线程中添加同步任务,并不会阻塞主线程上的任务执行完毕,因此结果会和”同步”+”串行”一致。
GCD的基本使用
GCD使用步骤分两步:
1、获取一个队列
2、将任务添加到队列中
系统会自动调度任务,通常是FIFO(先来先服务)
1、获取队列
// DISPATCH_QUEUE_SERIAL 串行
// DISPATCH_QUEUE_CONCURRENT 并行
dispatch_queue_create("队列标识符",队列类型);
我们可以使用dispatch_queue_create
来创建队列,队列类型有两种:DISPATCH_QUEUE_SERIAL
串行队列,DISPATCH_QUEUE_CONCURRENT
并行队列
GCD为我们提供了两种快捷获取队列的方式,一个是主列队(串行队列),一种是全局队列(并行队列)
1.1、获取主队列
dispatch_get_main_queue();
主队列中任务都会放到主线程中执行,并且是顺序执行
1.2、获取全局并发队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数是队列的优先级,一般选择默认即可; DISPATCH_QUEUE_PRIORITY_HIGH
高 DISPATCH_QUEUE_PRIORITY_DEFAULT
默认 DISPATCH_QUEUE_PRIORITY_LOW
低 DISPATCH_QUEUE_PRIORITY_BACKGROUND
后台
2、创建任务的方法
GCD为我们提供了创建同步和异步的方法,分别是dispatch_sync
和dispatch_async
,其中第一个参数是队列,第二个是需要执行的block块,我们的任务就放在这里
// 创建同步任务
dispatch_sync(queue, ^{
// 同步任务
});
// 创建异步任务
dispatch_async(queue, ^{
// 异步任务
});
GCD线程通信
在开发过程中,我们通常将一些耗时的操作放在子线程,如数据请求,文件下载等,当这些任务完成后,我们需要即使的更新到UI上,这时我们就需要回到主线程
//获取【并行】队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建【异步】任务
dispatch_async(queue, ^{
// do something ...
[NSThread sleepForTimeInterval:2.0];
// 回到主线程
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main, ^{
// 更新UI
});
});
整个过程如上述代码所示,我们获取并行队列,创建异步任务,但完成耗时操作后,再获取到主线程,将更新UI的任务添加到主线程上去。
即
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main, ^{
// 更新UI
});
验证组合
(1)【异步】+【并行】
NSLog(@"current thread:%@",[NSThread currentThread]);
// 创建【并行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// 开启多个【异步】任务
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
我们发现,”end”在线程输出之前,说明当前线程并未等待,而是开启了新的线程执行任务(3、4、5号线程);任务1、2、3交替完成,说明并发队列在同时执行多个任务
(2)【异步】+【串行】
我们将【并行】队列该为【串行】队列
NSLog(@"current thread:%@",[NSThread currentThread]);
// 创建【串行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 开启多个【异步】任务
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
我们发现,虽然开启了新的线程,但是队列是一个串行队列,因此任务是顺序执行的:1->2->3,另外,我们注意到,串行队列下,dispatch_async只会开启一个线程
(3)【同步】+【并行】
我们再稍稍改动代码,从而产生并行队列,同步任务
NSLog(@"current thread:%@",[NSThread currentThread]);
// 创建【并行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// 开启多个【同步】任务
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
我们可以看到,虽然是并行队列,但是dispatch_sync
同步的条件使得任务并没有开启新的线程,而是在当前线程(例子中是主线程)执行,并且按照顺序执行,另外,我们可以看到,”end”是在最后才输出,这就是同步的原因
(4)【同步】+【串行】
NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取【串行】队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 开启多个【同步】任务
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
我们发现,同步+串行和同步+并行的结果是一致的。
最后,我们来看下,比较特殊的串行队列的主队列
(5)【异步】+【主队列】
NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 开启多个【异步】任务
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
我们发现,结果似乎和【异步】+【串行】一样,其实主队列就是一种串行队列,不同的是,主队列并不会开启新的线程,所有的任务都会放在主线程中完成,并且服从FIFO先来先服务的原则
(6)【同步】+【主队列】
NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 开启多个【同步】任务
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
呃呃。。。我们发现,在主线程中,【同步】+【主队列】的方式发生了死锁,这个是因为dispatch_sync
将任务添加到主队列中,任务block部分需要等待主线程上的任务执行完毕之后才会执行,但是由于dispatch_sync
会阻塞当前线程,直到之前的任务都完成才会继续执行,这导致主线程的任务永不能完成,任务block里的代码也用不能被执行,从而产生了死锁
既然dispatch_sync
会阻塞当前线程,那我们将其放在子线程中会怎么样呢?
我们开启一个子线程测试
[NSThread detachNewThreadSelector:@selector(mainThreadVSSync) toTarget:self withObject:nil];
在子线程任务中,重新执行【同步】+【主队列】任务
-(void)mainThreadVSSync{
NSLog(@"current thread:%@",[NSThread currentThread]);
// 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 开启多个【同步】任务
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"3:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
我们发现,程序可以继续执行。我们使用【同步】+【主队列】时,线程不在是主线程,而是3号线程,这样dispatch_sync
开启同步任务时,并不会影响到主线程,同步任务可以继续执行,只不过都是在主线程中,而且是顺序执行
总结
1、GCD方式实现多线程多任务必须是【异步】+【并行队列】,缺一不可,其他方式的任务都是顺序执行的,无论异步还是同步
2、主队列是一种串行队列,切忌在主线程中使用【同步】+【主队列】的方式开启任务,会出现死锁
GCD其他
1、队列组:dispatch_group和dispatch_group_notify
有时,我们需要开启多个异步任务,并且所有任务都结束之后,再回到主线程执行任务,那么该如何做呢?这里我们就需要dispatch_group了
步骤:创建group->关联任务、队列、group->接受group通知
a、我们通过dispatch_group_create()
创建一个队列组
b、使用dispatch_group_async
方法,将任务放在队列中,队列则关联到group
c、接收完成通知dispatch_group_notify
// 创建一个队列组
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, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
// 另外一个耗时任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
// 完成通知,在主队列中完成UI更新
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"更新UI操作");
NSLog(@"%@",[NSThread currentThread]);
});
我们可以看到,只有当多线程中多任务完成后,dispatch_group_notify
中更新UI的操作才会被执行
2、dispatch_group_wait
当方法会阻塞当前线程,等待指定当group中当任务执行完成后,才会继续执行
1中的例子,也可以使用该方法达到同样的效果
// 创建一个队列组
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, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
}
});
// 另外一个耗时任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"2:%@",[NSThread currentThread]);
}
});
// 阻塞当前线程
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"更新UI操作");
NSLog(@"%@",[NSThread currentThread]);
但是,需要注意的是,dispatch_group_wait
是会阻塞当前线程的,而dispatch_group
则不会
3、延迟执行:dispatch_after
我们可以使用GCD快速的创建一个延迟执行的任务。当然,由于是添加到主队列的中的,因此这个延迟的时间是不准确的,这里还包括了队列前的任务时长
NSLog(@"执行前:%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"执行时:%@",[NSThread currentThread]);
});
我们发现,时差为2.2的左右,是大于我们所设的2.0秒的
4、一次性代码:dispatch_once
GCD可以创建一次性代码,在制作单例时,我们常常使用到它dispatch_once
,该函数可以保证某段代码在程序中只执行1次,并且在多线程的环境下,也可以保证线程安全
-(void)onceTask{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 一次性任务
});
}
5、快速迭代方法:dispatch_apply
通常我们会使用for循环遍历数组,GCD中的dispatch_apply
提供了类似的方法,不同的是,dispatch_apply
不仅可以是顺序的遍历,还可以是并发的遍历,主要看队列是串行的还是并行的
NSLog(@"apply---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
我们发现遍历并不是顺序执行的,如果使用的谁串行队列,和使用for循环遍历是一样的效果
6、信号量:dispatch_semaphore
信号量类似生活当中的信号灯,红灯停,绿灯行。GCD中的信号量Dispatch Semaphore是持有计数的信号,计数为0时等待,不可通行,计数为1或者大于1时,计数减1且允许通过。
dispatch_semaphore有三个函数,分别用来创建信号量,增加计数量
dispatch_semaphore_create
:创建并初始化信号的总量 dispatch_semaphore_signal
:发送一个信号,让信号总量加1 dispatch_semaphore_wait
:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行
使用信号量时,需要清除的分清等待和执行的线程
应用:
1、保持线程同步,将异步执行的任务转为同步执行任务
2、保证线程安全,为线程加锁
应用一:异步转同步
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore end");
我们在当前线程中开启了一个异步并行队列任务,由于我们使用了信号量semaphore
(其初始值为0,不可通行),当我们使用dispatch_semaphore_wait
,会阻塞当前线程,直到信号量不为0时,才会执行NSLog(@"semaphore end")
的输出,上述例子中,并没有给信号量计数器加1,因此不会执行后面输出任务
我们为其添加加一
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"1:%@",[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore end");
此时我们发现,程序正常输出。另外,我们这里通过通过信号量实现线程的同步操作(输出在异步任务之后),原本的异步线程在这里并没有什么效果,和同步任务没有任何区别
应用二:线程安全
结合GCD的信号量的特性,我们还可以使用其达到线程安全的目的,即在多线程下,保证事务的原子性
假设我分别开启两个线程去做加一操作,在不保证线程安全的情况下,势必会出现线程争抢资源,导致意想不到的错误产生
首先我们来看下非线程安全
dispatch_queue_t queue1 = dispatch_queue_create("one", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("two", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
[self addCount];
});
dispatch_async(queue2, ^{
[self addCount];
});
-(void)addCount{
while (1) {
if (count>=100) {
break;
}else{
count++;
NSLog(@"%ld-%@",count,[NSThread currentThread]);
}
}
}
我们发现,在此次过程中,线程3和4发生了资源争抢的问题,导致在加1的过程中发生了错误,两个线程累加的次数总和超过了100次
接下来,我们使用信号量来保证事务的原子性
// 创建信号量
semaphore = dispatch_semaphore_create(1);
// 在事务的开始和结束时操作信号量
-(void)addCount{
while (1) {
// 信号量减一,进入等待状态
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (count>=100) {
// 信号量加一,进入可通行状态
dispatch_semaphore_signal(semaphore);
break;
}else{
count++;
NSLog(@"%ld-%@",count,[NSThread currentThread]);
}
// 信号量加一,进入可通行状态
dispatch_semaphore_signal(semaphore);
}
}
我们发现,线程3和4交替操作累加,数字累计达到100时,操作总和为100,因此我们可以认定,信号量起到了很好的效果,保证了事务的原子性
上一篇: dom4j解析dom示例
下一篇: IOS 多线程编程指南_GCD