なぜ私はデッドロックですか?なぜdispatch_onceでデッドロックが発生するのですか?
- (void)foo
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self foo];
});
// whatever...
}
最初の呼び出しでfoo
が2回実行されると思います。
なぜ私はデッドロックですか?なぜdispatch_onceでデッドロックが発生するのですか?
- (void)foo
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self foo];
});
// whatever...
}
最初の呼び出しでfoo
が2回実行されると思います。
既存の回答は全く正確ではありません(1つは間違っています、もう1つは誤解を招くものであり、いくつかの重要な情報が欠落しています)。まずは、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
は最初の発信者&dow
のスタック上のアドレスを指すようにNULL
の初期状態から変更された(呼び出しこの呼び出し元1)。これはブロックが呼び出される前にの前にが発生します。ブロックが完了する前にもっと多くの発信者が到着すると、ブロックが完了するまで(ブロックされた発信者2..N)、その頭はonceToken
に含まれるウェイターのリンクリストに追加されます。このリストに追加された後、呼び出し側2..Nは、ブロック1の呼び出しを完了するために呼び出し側1のセマフォを待つ。その時点で、呼び出し側1は呼び出し元2に対してセマフォをシグナリングするリンクリストを呼び出す。その歩行の開始時にonceToken
がに変更され、はDISPATCH_ONCE_DONE
になります(便宜上、有効なポインタではない値であると定義されているため、ブロックされた呼び出し元のリンクリストのヘッドになることはありません)。それをDISPATCH_ONCE_DONE
にすることは、完了した状態をチェックするために後続の呼び出し元(プロセスの残りの期間)にとって安価なものです。
だからあなたの場合には、何が起こっていることはこれです:
-foo
を呼んで初めて、onceToken
(0に初期化されることが保証されている静力学によって保証されている)nilで、かつアトミックになりますリンクされたウェイターのリストになるように変更されました。-foo
を再帰的に呼び出すと、スレッドは「2番目の呼び出し元」とみなされ、この新しい下部スタックフレームに存在するウェイター構造がリストに追加され、次に待機しますセマフォ上でつまり、あなたはデッドロックされており、実際には「dispatch_once
ブロックに再帰的にコールしようとしないでください」となっています。しかし、問題は、ほとんど間違いなくませ「無限再帰」であり、フラグが最も確実ブロックが実行を完了した後にのみは変更されません - 前のブロックの実行、それを変更することは、発信者を作るために知っているか正確です2.Nは発呼者1が終了するのを待つ。
あなたが呼び出しはブロックの外側で、デッドロックがありませんように、このような何か、コードを少し変更することができます:それをしない、
- (void)foo
{
static dispatch_once_t onceToken;
BOOL shouldRunTwice = NO;
dispatch_once(&onceToken, ^{
shouldRunTwice = YES;
});
if (shouldRunTwice) {
[self foo];
}
// whatever...
}
は、任意のブレーク条件なしで再帰的方法であるように思わ! – duDE
なぜfooを2回呼び出す必要がありましたか? – manujmv
なぜあなたはそれを再帰的に呼びたいのですか?!?!? – hfossli