dispatch_queue_set_specific与获取当前队列

时间:2013-12-31 17:23:56

标签: ios objective-c-blocks grand-central-dispatch deadlock

我试图了解这两者之间的区别和用法:

static void *myFirstQueue = "firstThread";

dispatch_queue_t firstQueue = dispatch_queue_create("com.year.new.happy", DISPATCH_QUEUE_CONCURRENT);

dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

问题#1

这有什么区别:

dispatch_sync(firstQueue, ^{

    if(dispatch_get_specific(myFirstQueue))
    {
        //do something here
    }
});

以及以下内容:

dispatch_sync(firstQueue, ^{

    if(firstQueue == dispatch_get_current_queue())
    {
       //do something here
    }
});

问题#2:

而不是在

中使用上述(void*) myFirstQueue
dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

我们可以改用static int * myFirstQueue = 0;吗?

我的推理基于以下事实:

dispatch_once_t 0 (这里有相关吗?顺便说一下,我仍然不明白为什么dispatch_once_t必须初始化为0,尽管我已经在SO)上阅读了这些问题。

问题#3

你能举一个GCD死锁的例子吗?

问题#4

这可能有点太多了;无论如何,我会问,以防有人碰巧知道这个问题。如果没有,可以将此部分保留为未答复。

我没试过这个,因为我真的不知道怎么回事。但我的理念是:

无论如何,我们可以在某个队列中“放置一个句柄”,使我们仍然可以在其上保留句柄,从而能够检测队列分离后何时发生死锁;当有,并且由于我们得到了我们之前设置的队列句柄,我们可以以某种方式做一些事情来解锁僵局?

同样,如果要回答太多,或者如果我的推理完全可撤销/关闭(在问题#4 中),请随意将此部分留答。

新年快乐。


@ san.t

使用 static void *myFirstQueue = 0;

我们这样做:

dispatch_queue_set_specific(firstQueue, &myFirstQueue, &myFirstQueue, NULL);

完全可以理解。

但如果我们这样做:

static void *myFirstQueue = 1; 
//or any other number other than 0, it would be OK to revert back to the following?
dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

关于 dispatch_once_t

你能否详细说明一下:

为什么dispatch_once_t必须先 0 ,以及在以后阶段需要如何以及为什么需要充当布尔值?这是否与内存/安全性或前一个内存地址被其他非0(nil)对象占用的事实有关?

关于问题#3:

对不起,我可能不太清楚:我并不是说我遇到了僵局。我的意思是,是否有人可以通过GCD向我展示导致陷入僵局的代码。

最后

希望你能回答问题#4。如果没有,如前所述,没关系。

2 个答案:

答案 0 :(得分:51)

首先,我真的不认为你打算让那个队列同时进行。 dispatch_sync()并发队列实际上并没有完成任何事情(并发队列不保证在它们之间运行的块之间的排序)。因此,本答案的其余部分假定您打算在那里有一个串行队列。另外,我将以一般性的方式回答这个问题,而不是你的具体问题;希望没关系:))

以这种方式使用dispatch_get_current_queue()存在两个基本问题。一个非常宽泛的可以概括为“递归锁定是一个坏主意”,一个特定于调度的可以概括为“你可以并且通常会有多个当前队列”。

问题#1:递归锁定是一个坏主意

私有串行队列的通常目的是保护代码的不变量(“不变”是“必须为真的”)。例如,如果您使用队列来保护对属性的访问以使其具有线程安全性,则不变量是“此属性没有无效值”(例如:如果属性是结构,则为一半)如果它是从两个线程同时设置的,那么struct可以有一个新值,一半可以有旧值。一个串口队列强制一个线程或另一个线程完成整个struct的设置,然后另一个线程开始。) p>

我们可以推断,为了理所当然,在开始在串行队列上执行块时,必须保持不变量(否则,它显然没有受到保护)。一旦块开始执行,它就可以打破不变量(比如设置属性)而不用担心会弄乱任何其他线程,只要在它返回时再次保持不变量(在这个例子中,属性必须是完全的)设定)。

总结只是为了确保你仍然关注:在串行队列的每个块的开头和结尾,队列保护的不变量必须保持。在每个区块的中间,它可能会被打破。

如果在块中,你调用的东西试图使用受队列保护的东西,那么你已经将这个简单的规则改为一个更复杂的规则:而不是“在每个块的开头和结尾“它是”在开头,结尾,以及该块调用外部的任何东西“。换句话说,您现在必须检查每个块的每个单独的行,而不是考虑块级别的线程安全性。

这与dispatch_get_current_queue()有什么关系?在这里使用dispatch_get_current_queue()的唯一原因是检查“我们已经在这个队列中了吗?”,如果你已经在当前队列中,那么你已经处于可怕的情况之上了!所以不要这样做。使用私有队列来保护事物,不要从内部调用其他代码。你应该已经知道“我在这个队列中吗?”的答案。它应该是“不”。

这是dispatch_get_current_queue()被弃用的最大原因:阻止人们尝试使用它来模拟递归锁定(我上面已经描述过)。

问题2:您可以拥有多个当前队列!

考虑以下代码:

dispatch_async(queueA, ^{
    dispatch_sync(queueB, ^{
        //what is the current queue here?
    });
});

显然,queueB是最新的,但我们仍然在队列A! dispatch_sync导致queueA上的工作等待queueB上的工作完成,因此它们都是有效的“当前”。

这意味着此代码将死锁:

dispatch_async(queueA, ^{
    dispatch_sync(queueB, ^{
        dispatch_sync(queueA, ^{});
    });
});

您还可以使用目标队列来拥有多个当前队列:

dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
    dispatch_sync(queueA, ^{ /* deadlock! */ });
});

这里真正需要的是假设的“dispatch_queue_is_synchronous_with_queue(queueA, queueB)”,但由于这只会对实现递归锁定有用,而且我已经描述了这是一个坏主意......它不太可能待补充。

请注意,如果您只使用dispatch_async(),那么您就可以免受死锁的影响。可悲的是,你对种族条件完全没有免疫力。

答案 1 :(得分:4)

问题1 : 这两个代码片断做了同样的事情,当块确实在firstQueue中运行时,“有些工作”。但是,他们使用不同的方式来检测它是否在firstQueue上运行,第一个使用特定密钥(NULL)设置非(void*)myFirstQueue上下文(myFirstQueue)检查上下文是否确实是非NULL;第二个使用现已弃用的函数dispatch_get_current_queue进行检查。第一种方法是优选的。但是对我来说似乎没有必要,dispatch_sync保证块已经在firstQueue中运行。

问题2 : 仅使用static int * myFirstQueue = 0;是不行的,这样,myFirstQueueNULL指针而dispatch_queue_set_specific(firstQueue, key, context, NULL);需要非NULL key& context工作。但是它会像这样做一些小改动:

static void *myFirstQueue = 0;
dispatch_queue_t firstQueue = dispatch_queue_create("com.year.new.happy", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_set_specific(firstQueue, &myFirstQueue, &myFirstQueue, NULL);

这将使用myFirstQueue变量的地址作为键和上下文。

如果我们这样做:

static void *myFirstQueue = 1; 
//or any other number other than 0, it would be OK to revert back to the following?
dispatch_queue_set_specific(firstQueue, myFirstQueue, (void*) myFirstQueue, NULL);

我想这没关系,因为如果最后myFirstQueue参数为destructor

,则NULL指针不会被取消引用

dispatch_once_t也是0与此无关。它首先为0,并且在它被调度一次之后,它的值将变为非零,基本上作为布尔值。

以下是once.h的摘录,您可以看到dispatch_once_t实际上是long,Apple的实施细节要求它最初为0,可能是因为静态和放大;全局变量默认为零。你可以看到有一条线:

if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {

在调用once predicate函数之前,基本上检查dispatch_once仍为零。它与记忆安全无关。

/*!
 * @typedef dispatch_once_t
 *
 * @abstract
 * A predicate for use with dispatch_once(). It must be initialized to zero.
 * Note: static and global variables default to zero.
 */
typedef long dispatch_once_t;

/*!
 * @function dispatch_once
 *
 * @abstract
 * Execute a block once and only once.
 *
 * @param predicate
 * A pointer to a dispatch_once_t that is used to test whether the block has
 * completed or not.
 *
 * @param block
 * The block to execute once.
 *
 * @discussion
 * Always call dispatch_once() before using or testing any variables that are
 * initialized by the block.
 */
#ifdef __BLOCKS__
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
_dispatch_once(dispatch_once_t *predicate, dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    }
}
#undef dispatch_once
#define dispatch_once _dispatch_once
#endif

问题3 : 假设myQueue是串行的,并发队列就可以了。

dispatch_async(myQueue, ^{
    dispatch_sync(myQueue, ^{
        NSLog(@"This would be a deadlock");
    });
});

问题4 :不确定。