ios - 为什么我会因 dispatch_once 而陷入僵局?

标签 ios objective-c macos grand-central-dispatch semaphore

为什么我会陷入僵局?

- (void)foo
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        [self foo];

    });

    // whatever...
}

我希望 foo 在第一次调用时执行两次。

最佳答案

现有的答案都不太准确(一个完全错误,另一个有点误导并遗漏了一些关键细节)。首先,让我们去right to the source :

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 更改为指向第一个堆栈上的地址caller &dow(将此调用者称为 1)。这发生在调用 block 之前。如果在 block 完成之前有更多调用者到达,它们将被添加到等待者链表中,其头部包含在 onceToken 中,直到 block 完成(称它们为调用者 2..N)。添加到此列表后,调用者 2..N 在信号量上等待调用者 1 完成该 block 的执行,此时调用者 1 将遍历链表,为每个调用者 2..N 发送一次信号量。在那次行走的开始,onceToken再次更改为 DISPATCH_ONCE_DONE(它被方便地定义为一个永远不可能有效的值指针,因此永远不会成为被阻止调用者链表的头部。)将其更改为 DISPATCH_ONCE_DONE 可以让后续调用者(在进程的剩余生命周期内)检查变得便宜完成状态。

所以在你的情况下,发生的事情是这样的:

  • 第一次调用 -foo 时,onceToken 为 nil(这是通过静态保证被初始化为 0 来保证的),并自动更改为成为服务员链表的头。
  • 当您从 block 内部递归调用 -foo 时,您的线程被认为是“第二个调用者”,并且存在于这个新的较低堆栈框架中的等待程序结构被添加到列表,然后你去等待信号量。
  • 这里的问题是这个信号量永远不会发出信号,因为为了让它发出信号,你的 block 必须完成执行(在较高的堆栈帧中),现在由于死锁而不会发生。

所以,简而言之,是的,你陷入了僵局,这里的实际要点是,“不要尝试递归调用 dispatch_once block 。”但问题绝对不是“无限递归”,并且标志绝对不是在 block 完成执行后更改 - 之前更改它em> block 的执行正是它如何知道让调用者 2..N 等待调用者 1 完成。

关于ios - 为什么我会因 dispatch_once 而陷入僵局?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19176219/

相关文章:

ios - 如何使用 swift 将 MKPolyline 属性存储为可转换的 IOS 核心数据?

ios - 应用程序通知中心关闭时 UILocalNotification 仍然显示

excel - 刷新 QueryTable 抛出 "General ODBC error"- VBA Excel 2011 for Mac

objective-c - UIImage 导致内存泄漏

ios - 删除应用程序后的应用程序首选项

ios - 有什么办法可以在 iOS 的用户系统设置中添加语言吗?

ios - 尝试使用 QuartzCore 在饼图上实现偏移半径

ios - UITableViewCONtroller 中的快速多个单元格子类

java - 如何将 midi 从 java 程序发送到 OSX 上的 IAC 总线

c++ - 使用 CMake 和 GCC 在 Mac 上构建静态库?