为什么我们不能用一个dispatch_sync在当前队列?为什么我们不能用一个dispatch_sy

2019-05-17 15:44发布

我遇到了这样一个场景,我有可能在任一主线程或其他线程发生委托回调,而我不知道哪个直到运行时(使用StoreKit.framework )。

我也有UI代码,我需要在回调函数执行前发生哪些需要更新,所以我最初的想法是有这样的功能:

-(void) someDelegateCallback:(id) sender
{
    dispatch_sync(dispatch_get_main_queue(), ^{
        // ui update code here
    });

    // code here that depends upon the UI getting updated
}

当它在后台线程中执行的伟大工程。 然而,当主线程上执行,该程序涉及到一个僵局。

这本身似乎对我有意思,如果我读的文档dispatch_sync正确的,那么我希望它只是执行彻底,不担心它安排到runloop块,如说在这里 :

作为一种优化,这个函数调用可能在当前线程上的块时。

但是,这还不算什么大不了的事,它只是意味着多一点打字,这导致我这个方法:

-(void) someDelegateCallBack:(id) sender
{
    dispatch_block_t onMain = ^{
        // update UI code here
    };

    if (dispatch_get_current_queue() == dispatch_get_main_queue())
       onMain();
    else
       dispatch_sync(dispatch_get_main_queue(), onMain);
}

然而,这似乎有点倒退。 这是在GCD的决策错误,或者是没有办法,我在我的文档丢失的东西吗?

Answer 1:

我看到这个文件(最后一章) :

不要叫从上您传递给你的函数调用相同的队列执行任务的dispatch_sync功能。 这样做会死锁的队列。 如果您需要分派到当前队列,这样做异步使用dispatch_async功能。

另外,我跟着你提供,并在dispatch_sync我读这个的说明中的链接:

调用此函数并在僵局针对当前队列结果。

因此,我不认为这是有一个GCD的问题,我认为唯一明智的做法是,你发现问题后,发明了一个。



Answer 2:

dispatch_sync做两件事情:

  1. 排队一个块
  2. 阻止当前线程,直到该块完成运行

鉴于主线程是一个串行队列(这意味着它仅使用一个线程),下面的语句:

dispatch_sync(dispatch_get_main_queue(), ^(){/*...*/});

将导致以下事件:

  1. dispatch_sync队列在主队列块。
  2. dispatch_sync块主队列的线程,直到块执行完毕。
  3. dispatch_sync因为其中块应该运行的线程被阻塞永远等待。

理解这一点的关键是dispatch_sync不执行块,它只是他们排队。 执行将发生在运行循环的未来迭代。

下面的方法:

if (queueA == dispatch_get_current_queue()){
    block();
} else {
    dispatch_sync(queueA,block);
}

是完全没有问题的,但要知道,它不会保护你免受涉及队列的层次结构复杂的场景。 在这种情况下,当前队列可能比以前阻塞队列,你要发送的块不同。 例:

dispatch_sync(queueA, ^{
    dispatch_sync(queueB, ^{
        // dispatch_get_current_queue() is B, but A is blocked, 
        // so a dispatch_sync(A,b) will deadlock.
        dispatch_sync(queueA, ^{
            // some task
        });
    });
});

对于复杂的情况下,读/写在调度队列键值数据:

dispatch_queue_t workerQ = dispatch_queue_create("com.meh.sometask", NULL);
dispatch_queue_t funnelQ = dispatch_queue_create("com.meh.funnel", NULL);
dispatch_set_target_queue(workerQ,funnelQ);

static int kKey;

// saves string "funnel" in funnelQ
CFStringRef tag = CFSTR("funnel");
dispatch_queue_set_specific(funnelQ, 
                            &kKey,
                            (void*)tag,
                            (dispatch_function_t)CFRelease);

dispatch_sync(workerQ, ^{
    // is funnelQ in the hierarchy of workerQ?
    CFStringRef tag = dispatch_get_specific(&kKey);
    if (tag){
        dispatch_sync(funnelQ, ^{
            // some task
        });
    } else {
        // some task
    }
});

说明:

  • 我创建了一个workerQ指向队列funnelQ队列。 在实际的代码,如果你有几个“工人”队列,并要恢复/暂停所有一次(这是实现由恢复/更新他们的目标,这是非常有用funnelQ队列)。
  • 我可以在任何时间点漏斗我的工人队列,所以要知道,如果他们漏斗与否,我标记funnelQ字“漏斗”。
  • 下山的路我dispatch_sync东西workerQ ,以任何理由我想dispatch_syncfunnelQ ,但避免dispatch_sync当前的队列,所以我检查了标签,并采取相应的行动。 因为Get走到层次结构,值将不被发现workerQ但它会在发现funnelQ 。 这是发现如果在层次结构中的任何队列是我们存储的值的一个方式。 因此,为了防止dispatch_sync当前的队列。

如果你想知道关于读/写上下文数据的功能,主要有三种:

  • dispatch_queue_set_specific :写入队列。
  • dispatch_queue_get_specific :从队列中读取。
  • dispatch_get_specific :方便的功能,从当前队列中读取。

关键是通过指针比较,从来没有解除引用。 在二传手的最后一个参数是释放键析构函数。

如果你想知道“她指着一个排队的另一个”,就意味着这一点。 例如,我可以指出队列中的主队列,这将导致在队列中的所有块主队列(一般情况下是UI更新完成)运行。



Answer 3:

我知道你的困惑来自:

作为一种优化,这个函数调用可能在当前线程上的块时。

小心,它说, 当前线程

线程!=队列

队列不拥有线程和线程未绑定到一个队列。 有线程和有队列。 每当一个队列要运行一个块,它需要一个线程,但不会永远是同一个线程。 它只是需要它的任何线程(这可能是每次不同的一个),当它完成运行块(暂时),在同一个线程现在可以通过不同的队列中。

这句话谈到了优化是有关线程,不是队列。 例如,考虑你有两个串行队列, QueueAQueueB ,现在你做到以下几点:

dispatch_async(QueueA, ^{
    someFunctionA(...);
    dispatch_sync(QueueB, ^{
        someFunctionB(...);
    });
});

QueueA运行块,它会暂时拥有一个线程,任何线程。 someFunctionA(...)将执行该线程。 现在在做同步调度, QueueA不能做任何事情,它必须等待调度来完成。 QueueB ,另一方面,还需要一个线程来运行其块和执行someFunctionB(...) 因此,无论QueueA暂时停止其线程和QueueB使用了一些其他的线程来运行块或QueueA双手其线程到QueueB (毕竟它不会需要它反正直到同步调度已完成)和QueueB直接使用当前线程QueueA

不用说,最后一个选项的速度要快得多,因为不需要线程切换。 是一句谈到了优化。 因此,一个dispatch_sync()到不同的队列可能不会总是引起线程切换(不同的队列,也许同一个线程)。

dispatch_sync()仍不能发生在同一队列(同一个线程,是的,同一个队列,否)。 这是因为一个队列将块之后执行块,当它当前执行的块,它不会执行一个又一个,直到当前执行完成。 因此,执行BlockABlockA做了dispatch_sync()BlockB在同一个队列。 队列将不会运行BlockB只要它仍然运行BlockA ,但运行BlockA不会继续下去,直到BlockB已经跑了。 看到这个问题? 这是一个经典的僵局。



Answer 4:

该文件明确指出,传递当前队列会导致死锁。

现在,他们不说为什么他们设计的东西的方式(除了它实际上将采取额外的代码,使其工作),但我怀疑做的事情的原因这种方式是因为在这种特殊情况下,块是“跳楼”队列,即在正常情况下,你的块结束队列上的所有其它模块已经运行后运行,但在这种情况下,将之前运行。

当你试图使用GCD的互斥机制这个问题的产生,而这种特殊情况下等同于使用递归互斥。 我不想陷入争论它是否最好使用GCD或传统的互斥API,如并行线程互斥,甚至无论是使用递归互斥体是一个好主意; 我让别人争论这件事,但肯定是有这个需求,尤其是当它是你正在处理的主队列。

就个人而言,我认为dispatch_sync会比较有用的,如果它支持这样或如果有,所提供的替代行为的另一功能。 我将敦促其他人是这样认为的,以文件与苹果的bug报告(为我所做的一切,ID:12668073)。

您可以编写自己的函数做同样的,但它是一个黑客攻击的一位:

// Like dispatch_sync but works on current queue
static inline void dispatch_synchronized (dispatch_queue_t queue,
                                          dispatch_block_t block)
{
  dispatch_queue_set_specific (queue, queue, (void *)1, NULL);
  if (dispatch_get_specific (queue))
    block ();
  else
    dispatch_sync (queue, block);
}

NB以前,我有这样的使用dispatch_get_current_queue(),但现在已经被弃用的例子。



Answer 5:

dispatch_asyncdispatch_sync执行推动他们的动作到所需的队列。 该行动不会立即发生; 它发生在未来的某个队列的运行循环迭代。 之间的差dispatch_asyncdispatch_syncdispatch_sync块当前队列中,直到结束动作。

想想当你在当前队列异步执行的东西会发生什么。 再次,它不会立即发生; 它把它在一个FIFO队列,并且它必须等待,直到运行循环的当前迭代完成后(也可能是等待是在队列中的其他操作前要把这个新的行动)。

现在你可能会问,在当前队列执行操作异步,为什么不永远只是直接调用该函数的时候,而不是等到将来的某个时间。 答案是,有两者之间有很大的区别。 很多时候,你需要执行的操作,但它需要任何的副作用是由函数的堆栈在运行循环的当前迭代完成进行; 或者你需要在这之后已定于在运行循环的一些动画动作,等等。这就是为什么很多时候,你会看到代码来执行你的动作[obj performSelector:selector withObject:foo afterDelay:0]是的,这是不同的从[obj performSelector:selector withObject:foo]

正如我们之前所说, dispatch_sync是一样的dispatch_async ,不同的是它阻止,直到动作完成。 所以这是显而易见的,为什么它会死锁 - 块无法执行,直到运行循环的当前迭代结束后至少; 但我们正在等待它继续之前完成。

从理论上讲,将有可能使一个特殊的情况下dispatch_sync为当它是当前线程,立即执行。 (存在这样的特殊情况下performSelector:onThread:withObject:waitUntilDone:当线程是当前线程和waitUntilDone:是YES,它会立即执行它),但我想苹果公司决定,这是更好地在这里有一致的行为不管队列。



Answer 6:

从下面的文件中找到。 https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_sync

不同于dispatch_async,“dispatch_sync”函数不返回,直到块已完成。 调用此函数并在僵局针对当前队列结果。

不像dispatch_async也没有保留在目标队列进行。 因为这个函数调用是同步的,它“ ”对方的参考。 此外,没有Block_copy在块上执行。

作为一种优化,这个函数调用可能在当前线程上的块时。



文章来源: Why can't we use a dispatch_sync on the current queue?