1.疑问
前几天看到有人在问,如何判断当前执行的队列是不是指定队列的问题。网上的资料有很多,但看完以后我反而整不明白了。判断方法有3种:
- 使用dispatch_get_current_queue获取当前执行的队列。
- 使用dispatch_queue_set_specific & dispatch_get_specific 标记并获取指定队列
- 使用dispatch_queue_get_label 获取队列标签,比较字符串判断。(SDWebImage中采用此法判断)
其中,dispatch_get_current_queue
在iOS6之后是被弃用的,苹果只推荐在打印中使用,原因是它容易导致死锁。百度上大部分资料也没有说清楚为什么会导致死锁。(什么叫死锁本篇文章不在详细解释,简单描述就是不同操作相互等待导致互相无法进行)那么问题来了,看下面2个例子:
例子1: dispatch_get_current_queue在一般情况下是否发生死锁
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
dispatch_sync(queueA /*(或者queueB)*/, ^{
/*function*/
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
if(dispatch_get_current_queue() == queueA) {
block();
}
else {
dispatch_sync(queueA, block);
}
});
将dispatch_sync闭包中执行的内容看作一个函数,那么有2种执行情况:
1.dispatch_sync(queueA ): dispatch_get_current_queue的返回值就是queueA,所以直接执行block中的代码块,并不会发生死锁。
2.dispatch_sync(queueB ): dispatch_get_current_queue的返回值是queueB,此时会执行else中的代码,同步到queueA任务队列中,执行block代码。这种情况两个队列之间各自执行自己的任务,所以也不会发生死锁。
例子2:dispatch_queue_set_specific & dispatch_get_specific处理一般情况下的队列判断
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
dispatch_sync(queueA, ^{
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
if (retrievedValue) {
block();
} else {
dispatch_sync(queueA, block);
}
});
先简单介绍一下dispatch_queue_set_specific
& dispatch_get_specific
,这个函数通过给队列设置标记,并且通过指定标记来获取当前当前队列是不是指定的队列。如果当前队列是,则会返回标记字符串"queueA",如果不是则返回nil。
同样,也有2种情况:
1.dispatch_sync(queueA ): dispatch_get_specific的返回值是"queueA",所以直接执行block中的代码块,并不会发生死锁。
2.dispatch_sync(queueB ): dispatch_get_specific的返回值是nil,此时会执行else中的代码,同步到queueA任务队列中,同上,也不会发生死锁。
看完这2个例子,我发现2种方法都可以正确检验队列,并没有发生传说中的死锁现象。那么为什么苹果要废弃
dispatch_get_current_queue
方法呢?
2.原因
When dispatch_get_current_queue() is called on the main thread, it may
or may not return the same value as dispatch_get_main_queue().
从苹果API上对dispatch_get_current_queue
中的描述可以看到,这个函数的返回值不一定是用户想要的那个返回值。但具体为什么,文档上也没有说清楚。
于是我们需要对GCD队列进行进一步的研究,在为什么dispatch_get_current_queue被废弃中找到了可以解释这一切的答案。
原来,队列之间也有指向关系,如图:
![](https://img.haomeiwen.com/i1616624/65a050b3b5c4a3f7.png)
可以看出,无论是串行还是并发队列,只要有targetq,都会一层一层地往上扔,直到线程池。所以无法单用某个队列对象来描述“当前队列”这一概念的
而队列的指向关系很可能被修改,而导致dispatch_get_current_queue
获取的队列和我们想要的不一致。当设置了B的target queue为A,那么代码中A B都可以看成是当前队列。
dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{ /* deadlock! */ });
});
而使用dispatch_get_current_queue
获取queueB就还是B,这种情况下就会出现死锁。
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
//注意此处,将B的target queue设置为A
dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
if(dispatch_get_current_queue() == queueA) {
block();
}
else {
dispatch_sync(queueA, block);
}
});
此时,dispatch_get_current_queue()的返回值是queueB,进入else中,同步像queueA添加任务,但这时候的dispatch_sync(queueB, ^{})相当于已经在queueA中添加任务,就导致了同步死锁。
3.解决
从上面我们知道了使用dispatch_get_current_queue()
在判断队列时候,可能因为队列层级而导致同步死锁。那么正确的判断方式则是使用dispatch_queue_set_specific
& dispatch_get_specific
,我们再来看一下使用它设置了目标队列情况下的处理。
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
dispatch_set_target_queue(queueB, queueA);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
dispatch_sync(queueA(或者queueB), ^{
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
if (retrievedValue) {
block();
} else {
dispatch_sync(queueA, block);
}
});
情况依然是2种:
- dispatch_sync(queueA)时,当前队列是queue, dispatch_get_specific将返回"queueA",执行block()不会死锁。
- dispatch_sync(queueB)时,queueB的目标队列是queueA,dispatch_get_specific将返回"queueA",依然执行block(),不会死锁。
- 综合之前的例子,再添加一个queueC,queueC不设置目标队列,dispatch_get_specific返回nil,会执行
dispatch_sync(queueA, block)
,但是从queueC同步到queueA中,所以并不会导致死锁。
4.总结
1.dispatch_get_current_queue()
可能会因为队列层级关系导致死锁。不是用来判断指定队列的正确方式
2.dispatch_queue_set_specific
& dispatch_get_specific
不会因为队列层级关系的变化而找到错误的队列。
3.dispatch_queue_get_label
虽然在SDWebImage缓存模块中使用来判断队列是不是ioQueue,但当队层级关系被改变时也会发生与dispatch_get_current_queue()
相同的问题。当然sd中没有指定特殊队列关系的情况下不会出现问题。大家可以试一下把下面代码粘到刚刚的例子里去试一下。
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
综上所述,判断指定队列正确的姿势应该是使用dispatch_queue_set_specific
& dispatch_get_specific
。
网友评论