我试图了解这两者之间的区别和用法:
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。如果没有,如前所述,没关系。
答案 0 :(得分:51)
首先,我真的不认为你打算让那个队列同时进行。 dispatch_sync()
并发队列实际上并没有完成任何事情(并发队列不保证在它们之间运行的块之间的排序)。因此,本答案的其余部分假定您打算在那里有一个串行队列。另外,我将以一般性的方式回答这个问题,而不是你的具体问题;希望没关系:))
以这种方式使用dispatch_get_current_queue()
存在两个基本问题。一个非常宽泛的可以概括为“递归锁定是一个坏主意”,一个特定于调度的可以概括为“你可以并且通常会有多个当前队列”。
私有串行队列的通常目的是保护代码的不变量(“不变”是“必须为真的”)。例如,如果您使用队列来保护对属性的访问以使其具有线程安全性,则不变量是“此属性没有无效值”(例如:如果属性是结构,则为一半)如果它是从两个线程同时设置的,那么struct可以有一个新值,一半可以有旧值。一个串口队列强制一个线程或另一个线程完成整个struct的设置,然后另一个线程开始。) p>
我们可以推断,为了理所当然,在开始在串行队列上执行块时,必须保持不变量(否则,它显然没有受到保护)。一旦块开始执行,它就可以打破不变量(比如设置属性)而不用担心会弄乱任何其他线程,只要在它返回时再次保持不变量(在这个例子中,属性必须是完全的)设定)。
总结只是为了确保你仍然关注:在串行队列的每个块的开头和结尾,队列保护的不变量必须保持。在每个区块的中间,它可能会被打破。
如果在块中,你调用的东西试图使用受队列保护的东西,那么你已经将这个简单的规则改为一个更复杂的规则:而不是“在每个块的开头和结尾“它是”在开头,结尾,以及该块调用外部的任何东西“。换句话说,您现在必须检查每个块的每个单独的行,而不是考虑块级别的线程安全性。
这与dispatch_get_current_queue()
有什么关系?在这里使用dispatch_get_current_queue()
的唯一原因是检查“我们已经在这个队列中了吗?”,如果你已经在当前队列中,那么你已经处于可怕的情况之上了!所以不要这样做。使用私有队列来保护事物,不要从内部调用其他代码。你应该已经知道“我在这个队列中吗?”的答案。它应该是“不”。
这是dispatch_get_current_queue()
被弃用的最大原因:阻止人们尝试使用它来模拟递归锁定(我上面已经描述过)。
考虑以下代码:
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;
是不行的,这样,myFirstQueue
是NULL
指针而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 :不确定。