GCD
1.主线程串行队列
//(主线程串行队列):与主线程功能相同,提交至Main queue的任务会在主线程中执行
dispatch_queue_t mainQueue =dispatch_get_main_queue();
//主线程串行队列同步执行任务,在主线程运行时,会产生死锁
dispatch_sync(mainQueue,^{
NSLog(@"MainQueue");
});
//执行异步不会产生死锁一般情况下,异步返回主线程,根据获取的数据,更新UI
dispatch_queue_t mainQueue1 =dispatch_get_main_queue();
dispatch_async(mainQueue1,^{
NSLog(@"MainQueue");
});
2.全局并发队列
//(全局并发队列):全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别
dispatch_queue_t globalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(globalQueue, ^{
NSLog(@"次加入全局并发队列");
});
3.自定义串行队列 DISPATCH_QUEUE_SERIAL
//获取自定义串行队列
dispatch_queue_t coustomQueue =dispatch_queue_create("coustomQueue",DISPATCH_QUEUE_SERIAL);
//打印出队列name
NSLog(@"%s",dispatch_queue_get_label(coustomQueue)) ;
//自定义串行队列嵌套执行同步任务,产生死锁
dispatch_queue_t serialQueue =dispatch_queue_create("com.dullgrass.serialQueue",DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{ //该代码段后面的代码都不会执行,程序被锁定在这里
NSLog(@"会执行的代码");
dispatch_sync(serialQueue, ^{
NSLog(@"代码不执行");
});
});
4.自定义并发队列 DISPATCH_QUEUE_CONCURRENT
//获取自定义并发队列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//自定义并发队列嵌套执行同步任务(不会产生死锁,程序正常运行)
dispatch_sync(conCurrentQueue, ^{
NSLog(@"先加入队列");
dispatch_sync(conCurrentQueue, ^{
NSLog(@"次加入队列");
});
});
//自定义并发队列执行异步任务并发队列中的任务,几乎是同步执行的,输出顺序不确定
dispatch_async(conCurrentQueue, ^{
NSLog(@"先加入队列");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"次加入队列");
});
5.队列组
//Group queue (队列组)
//当遇到需要执行多个线程并发执行,然后等多个线程都结束之后,再汇总执行结果时可以用group queue
dispatch_queue_t conCurrentGlobalQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_queue_t mainQueue2 =dispatch_get_main_queue();
dispatch_group_t groupQueue =dispatch_group_create();
NSLog(@"current task");
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务1");
});
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务2");
});
dispatch_group_notify(groupQueue, mainQueue2, ^{
NSLog(@"groupQueue中的任务都执行完成,回到主线程更新UI");
});
NSLog(@"next task");
6.常用dispatch 方法
//GCD中一些系统提供的常用dispatch方法
//dispatch_after延时添加到队列
dispatch_time_t delayTime3 =dispatch_time(DISPATCH_TIME_NOW,3*NSEC_PER_SEC);
dispatch_after(delayTime3, mainQueue, ^{
NSLog(@"3秒之后添加到队列");
});
//dispatch_apply在给定的队列上多次执行某一任务
dispatch_queue_t applyQueue =dispatch_get_global_queue(0,0);
dispatch_apply(3, applyQueue, ^(size_t index) {
NSLog(@"current index %@",@(index));
sleep(1);
});
//dispatch_once保证在app运行期间,block中的代码只执行一次
//单例
+ (ShareManager *)shareManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[self alloc] init];
});
return sharedManager;
}
//dispatch_barrier_async栅栏的作用
//等待在dispatch_barrier_async之前加入的队列全部执行完成之后(这些任务是并发执行的)再执行dispatch_barrier_async中的任务
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 2");
});
dispatch_barrier_async(conCurrentQueue, ^{
NSLog(@"dispatch barrier");
});
死锁一直都是在使用多线程时,需要注意的一个问题。以前对同步、异步,串行、并行只有一个模糊的概念,想想也是时候整理一下了。再看看之前的博客,已经很久没有干货了【说得好像之前有干货一样】,所以,这篇博客,我尽最大努力,也借鉴了很多其他博客中的例子,来讲解GCD死锁问题。
环境信息:
Mac OS X 10.10.5
Xcode 6.4
iOS 8.4
正文
串行与并行
在使用GCD的时候,我们会把需要处理的任务放到Block中,然后将任务追加到相应的队列里面,这个队列,叫做Dispatch Queue。然而,存在于两种Dispatch Queue,一种是要等待上一个执行完,再执行下一个的Serial Dispatch Queue,这叫做串行队列;另一种,则是不需要上一个执行完,就能执行下一个的Concurrent Dispatch Queue,叫做并行队列。这两种,均遵循FIFO原则。
举一个简单的例子,在三个任务中输出1、2、3,串行队列输出是有序的1、2、3,但是并行队列的先后顺序就不一定了。
那么,并行队列又是怎么在执行呢?
虽然可以同时多个任务的处理,但是并行队列的处理量,还是要根据当前系统状态来。如果当前系统状态最多处理2个任务,那么1、2会排在前面,3什么时候操作,就看1或者2谁先完成,然后3接在后面。
串行和并行就简单说到这里,关于它们的技术点其实还有很多,可以自行了解。
同步与异步
串行与并行针对的是队列,而同步与异步,针对的则是线程。最大的区别在于,同步线程要阻塞当前线程,必须要等待同步线程中的任务执行完,返回以后,才能继续执行下一任务;而异步线程则是不用等待。
仅凭这几句话还是很难理解,所以之后准备了很多案例,可以边分析边理解。
GCD API
GCD API很多,这里仅介绍本文用到的。
1. 系统标准提供的两个队列
1
2
3
4
5
|
// 全局队列,也是一个并行队列
dispatch_get_global_queue
// 主队列,在主线程中运行,因为主线程只有一个,所以这是一个串行队列
dispatch_get_main
_
queue
|
2. 除此之外,还可以自己生成队列
1
2
3
4
5
|
// 从DISPATCH_QUEUE_SERIAL看出,这是串行队列
dispatch_queue_create
(
"com.demo.serialQueue"
,
DISPATCH_QUEUE_SERIAL
)
// 同理,这是一个并行队列
dispatch_queue_create
(
"com.demo.concurrentQueue"
,
DISPATCH_QUEUE_CONCURRENT
)
|
接下来是同步与异步线程的创建:
1
2
3
|
dispatch_sync
(
.
.
.
,
^
(
block
)
)
// 同步线程
dispatch_async
(
.
.
.
,
^
(
block
)
)
// 异步线程
|
案例与分析
假设你已经基本了解了上面提到的知识,接下来进入案例讲解阶段。
案例一:
1
2
3
4
5
6
|
NSLog
(
@"1"
)
;
// 任务1
dispatch_sync
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
}
)
;
NSLog
(
@"3"
)
;
// 任务3
|
结果,控制台输出:
1
2
|
1
|
分析:
- dispatch_sync表示是一个同步线程;
- dispatch_get_main_queue表示运行在主线程中的主队列;
- 任务2是同步线程的任务。
首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面,问题来了:
任务3要等任务2执行完才能执行,任务2由排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。
案例二:
1
2
3
4
5
6
|
NSLog
(
@"1"
)
;
// 任务1
dispatch_sync
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_HIGH
,
0
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
}
)
;
NSLog
(
@"3"
)
;
// 任务3
|
结果,控制台输出:
1
2
3
4
|
1
2
3
|
分析:
首先执行任务1,接下来会遇到一个同步线程,程序会进入等待。等待任务2执行完成以后,才能继续执行任务3。从dispatch_get_global_queue可以看出,任务2被加入到了全局的并行队列中,当并行队列执行完任务2以后,返回到主队列,继续执行任务3。
案例三:
1
2
3
4
5
6
7
8
9
10
11
|
dispatch_queue_t
queue
=
dispatch_queue_create
(
"com.demo.serialQueue"
,
DISPATCH_QUEUE_SERIAL
)
;
NSLog
(
@"1"
)
;
// 任务1
dispatch_async
(
queue
,
^
{
NSLog
(
@"2"
)
;
// 任务2
dispatch_sync
(
queue
,
^
{
NSLog
(
@"3"
)
;
// 任务3
}
)
;
NSLog
(
@"4"
)
;
// 任务4
}
)
;
NSLog
(
@"5"
)
;
// 任务5
|
结果,控制台输出:
1
2
3
4
5
|
1
5
2
// 5和2的顺序不一定
|
分析:
这个案例没有使用系统提供的串行或并行队列,而是自己通过dispatch_queue_create函数创建了一个DISPATCH_QUEUE_SERIAL的串行队列。
- 执行任务1;
- 遇到异步线程,将【任务2、同步线程、任务4】加入串行队列中。因为是异步线程,所以在主线程中的任务5不必等待异步线程中的所有任务完成;
- 因为任务5不必等待,所以2和5的输出顺序不能确定;
- 任务2执行完以后,遇到同步线程,这时,将任务3加入串行队列;
- 又因为任务4比任务3早加入串行队列,所以,任务3要等待任务4完成以后,才能执行。但是任务3所在的同步线程会阻塞,所以任务4必须等任务3执行完以后再执行。这就又陷入了无限的等待中,造成死锁。
案例四:
1
2
3
4
5
6
7
8
9
10
|
NSLog
(
@"1"
)
;
// 任务1
dispatch_async
(
dispatch_get_global_queue
(
0
,
0
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
dispatch_sync
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@"3"
)
;
// 任务3
}
)
;
NSLog
(
@"4"
)
;
// 任务4
}
)
;
NSLog
(
@"5"
)
;
// 任务5
|
结果,控制台输出:
1
2
3
4
5
6
7
|
1
2
5
3
4
// 5和2的顺序不一定
|
分析:
首先,将【任务1、异步线程、任务5】加入Main Queue中,异步线程中的任务是:【任务2、同步线程、任务4】。
所以,先执行任务1,然后将异步线程中的任务加入到Global Queue中,因为异步线程,所以任务5不用等待,结果就是2和5的输出顺序不一定。
然后再看异步线程中的任务执行顺序。任务2执行完以后,遇到同步线程。将同步线程中的任务加入到Main Queue中,这时加入的任务3在任务5的后面。
当任务3执行完以后,没有了阻塞,程序继续执行任务4。
从以上的分析来看,得到的几个结果:1最先执行;2和5顺序不一定;4一定在3后面。
案例五:
1
2
3
4
5
6
7
8
9
10
11
12
|
dispatch_async
(
dispatch_get_global_queue
(
0
,
0
)
,
^
{
NSLog
(
@"1"
)
;
// 任务1
dispatch_sync
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
}
)
;
NSLog
(
@"3"
)
;
// 任务3
}
)
;
NSLog
(
@"4"
)
;
// 任务4
while
(
1
)
{
}
NSLog
(
@"5"
)
;
// 任务5
|
1
|
结果,控制台输出:
|
1
2
3
4
|
1
4
// 1和4的顺序不一定
|
分析:
和上面几个案例的分析类似,先来看看都有哪些任务加入了Main Queue:【异步线程、任务4、死循环、任务5】。
在加入到Global Queue异步线程中的任务有:【任务1、同步线程、任务3】。
第一个就是异步线程,任务4不用等待,所以结果任务1和任务4顺序不一定。
任务4完成后,程序进入死循环,Main Queue阻塞。但是加入到Global Queue的异步线程不受影响,继续执行任务1后面的同步线程。
同步线程中,将任务2加入到了主线程,并且,任务3等待任务2完成以后才能执行。这时的主线程,已经被死循环阻塞了。所以任务2无法执行,当然任务3也无法执行,在死循环后的任务5也不会执行。
最终,只能得到1和4顺序不定的结果。
基本概念
关于iOS开发中,多线程基本的概念和基本使用,我在这里就不在重复说了。但是为了照顾到有的同学可能还不是对基本的概念不熟悉,可以参考一下这篇文章并发其实很简单
说说信号量,并发数
如果你有计算机基础,那么下面这段话应该很简单就能理解
信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行是,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,知道信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1. 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.次数进程2咱有资源,排他访问资源。 这就是信号量来控制互斥的原理
简单来讲 信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。
当然再NSoperation下可以直接设置并发数,就没有这么麻烦了。
GCD如何使用信号量
我们使用GCD的时候如何让线程同步,目前我能想到的就三种
- 1.dispatch_group
- 2.dispatch_barrier
- 3.dispatch_semaphore
1和2比较简单,也是比较常用的。这里不在介绍。如果不清楚可以参考并发其实很简单 这里主要讲讲dispatch_semaphore
在GCD中有三个函数是semaphore的操作, 分别是:
- dispatch_semaphore_create 创建一个semaphore
- dispatch_semaphore_signal 发送一个信号
- dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
看代码
// 创建队列组 dispatch_group_t group = dispatch_group_create(); // 创建信号量,并且设置值为10 dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (int i = 0; i < 100; i++) { // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_group_async(group, queue, ^{ NSLog(@"%i",i); sleep(2); // 每次发送信号则semaphore会+1, dispatch_semaphore_signal(semaphore); }); }
上面的注释已经把如何通过控制信号量来控制线程同步解释的比较浅显了。关键就是这句:
// 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
实际应用
在开发中我们需要等待某个网络回调完之后才执行后面的操作,根据啥给你们分析的过程,可以写出如下代码,达到这种效果。
_block BOOL isok = NO; dispatch_semaphore_t sema = dispatch_semaphore_create(0); Engine *engine = [[Engine alloc] init]; [engine queryCompletion:^(BOOL isOpen) { isok = isOpen; dispatch_semaphore_signal(sema); } onError:^(int errorCode, NSString *errorMessage) { isok = NO; dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // todo what you want to do after net callback
在具体一点,做通讯录的时候需要判断权限,才能获取通讯录。如果我没记错。再iOS9之前可以通过如下方式获得。这也是我第一次实际应用信号量的时候。
获取通讯录//创建通讯簿的引用 addBook=ABAddressBookCreateWithOptions(NULL, NULL); //创建一个出事信号量为0的信号 dispatch_semaphore_t sema=dispatch_semaphore_create(0); //申请访问权限 ABAddressBookRequestAccessWithCompletion(addBook, ^(bool greanted, CFErrorRef error) { //greanted为YES是表示用户允许,否则为不允许 if (!greanted) { tip=1; } //发送一次信号 dispatch_semaphore_signal(sema); }); //等待信号触发 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
作者:纸简书生
链接:http://www.jianshu.com/p/04ca5470f212
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。