每日一问28——GCD(判断队列正确姿势与原因)

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被废弃中找到了可以解释这一切的答案。
原来,队列之间也有指向关系,如图:

955431-a3bcc180f2243ce0.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

相关文章:

为什么dispatch_get_current_queue被废弃

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,999评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,102评论 1 302
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,709评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,439评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,846评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,881评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,062评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,783评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,517评论 1 248
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,762评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,241评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,236评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,145评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,941评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,965评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,802评论 2 275

推荐阅读更多精彩内容

  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 995评论 0 4
  • iOS中GCD的使用小结 作者dullgrass 2015.11.20 09:41*字数 4996阅读 20199...
    DanDanC阅读 771评论 0 0
  • GCD笔记 总结一下多线程部分,最强大的无疑是GCD,那么先从这一块部分讲起. Dispatch Queue的种类...
    jins_1990阅读 720评论 0 1
  • 一、GCD的API 1. Dispatch queue 在执行处理时存在两种Dispatch queue: 等待现...
    doudo阅读 483评论 0 0
  • 很多人,变了就是变了,以前的我还会因此感到哀伤,现在的更是无力挽留,有些人,有些事也是想挽留但终究留不住的。明知心...
    雯子mosquito阅读 208评论 0 0