为什么我会陷入僵局?
- (void)foo
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self foo];
});
// whatever...
}
我预计foo
第一次调用时执行两次。
现有的答案都不是很准确(一个是完全错误的,另一个有点误导并且遗漏了一些关键细节)。首先,我们走吧源头权利:
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
struct _dispatch_once_waiter_s * volatile *vval =
(struct _dispatch_once_waiter_s**)val;
struct _dispatch_once_waiter_s dow = { NULL, 0 };
struct _dispatch_once_waiter_s *tail, *tmp;
_dispatch_thread_semaphore_t sema;
if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
dispatch_atomic_acquire_barrier();
_dispatch_client_callout(ctxt, func);
dispatch_atomic_maximally_synchronizing_barrier();
//dispatch_atomic_release_barrier(); // assumed contained in above
tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
tail = &dow;
while (tail != tmp) {
while (!tmp->dow_next) {
_dispatch_hardware_pause();
}
sema = tmp->dow_sema;
tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
_dispatch_thread_semaphore_signal(sema);
}
} else {
dow.dow_sema = _dispatch_get_thread_semaphore();
for (;;) {
tmp = *vval;
if (tmp == DISPATCH_ONCE_DONE) {
break;
}
dispatch_atomic_store_barrier();
if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
dow.dow_next = tmp;
_dispatch_thread_semaphore_wait(dow.dow_sema);
}
}
_dispatch_put_thread_semaphore(dow.dow_sema);
}
}
所以真正发生的是,与其他答案相反,onceToken
从其初始状态改变为NULL
指向第一个调用者的堆栈上的地址&dow
(将此呼叫者称为 1)。有时候是这样的before该块被调用。如果在块完成之前有更多调用者到达,它们将被添加到等待者的链表中,链表的头包含在onceToken
直到块完成(称它们为调用者 2..N)。添加到此列表后,调用者 2..N 等待调用者 1 的信号量来完成块的执行,此时调用者 1 将遍历链表,为每个调用者 2..N 发送信号量一次。在那次步行的开始,onceToken
被改变了again to be DISPATCH_ONCE_DONE
(它被方便地定义为一个永远不可能是有效指针的值,因此永远不可能成为被阻止的调用者链表的头。)将其更改为DISPATCH_ONCE_DONE
是什么使得后续调用者(在进程的剩余生命周期中)检查完成状态变得便宜。
所以就你而言,发生的事情是这样的:
- 第一次打电话的时候
-foo
, onceToken
为 nil(这是通过保证静态初始化为 0 来保证的),并且被原子地更改为成为服务员链表的头。
- 你打电话时
-foo
从块内部递归地,您的线程被认为是“第二个调用者”,并且存在于这个新的较低堆栈帧中的等待结构被添加到列表中,然后您去等待信号量。
- 这里的问题是,这个信号量永远不会被发出信号,因为为了让它被发出信号,你的块必须完成执行(在较高的堆栈帧中),而现在由于死锁而不会发生这种情况。
所以,简而言之,是的,你陷入了僵局,这里的实际要点是,“不要尝试递归地调用dispatch_once
阻止。”但问题绝对是NOT“无限递归”,而该标志绝对不是only块完成执行后更改 - 更改它before该块执行的是exactly它如何知道让调用者 2..N 等待调用者 1 完成。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)