なぜ私は行き詰まっているのですか?
- (void)foo
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self foo];
});
// whatever...
}
foo
最初の呼び出しで 2 回実行されることを期待しています。
なぜ私は行き詰まっているのですか?
- (void)foo
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self foo];
});
// whatever...
}
foo
最初の呼び出しで 2 回実行されることを期待しています。
既存の回答はどちらもまったく正確ではありません (1 つは完全に間違っており、もう 1 つは少し誤解を招きやすく、重要な詳細を見逃しています)。まず、ソースに行きましょう:
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
初期状態から変更されます(この呼び出し元を 1 と呼びます)。これは、ブロックが呼び出される前に発生します。ブロックが完了する前にさらに呼び出し元が到着すると、それらはウェイターのリンクされたリストに追加され、その先頭はブロックが完了するまで保持されます (呼び出し元 2..N と呼びます)。このリストに追加された後、呼び出し元 2..N は、呼び出し元 1 がブロックの実行を完了するまでセマフォで待機します。この時点で、呼び出し元 1 は、呼び出し元 2..N ごとに 1 回セマフォに信号を送るリンクされたリストを歩きます。その散歩の初めに、再びに変更されますNULL
&dow
onceToken
onceToken
DISPATCH_ONCE_DONE
DISPATCH_ONCE_DONE
(これは、有効なポインターになることは決してないため、ブロックされた呼び出し元のリンクされたリストの先頭になることは決してない値になるように便利に定義されています)。プロセスの存続期間) 完了状態を確認します。
したがって、あなたの場合、何が起こっているかは次のとおりです。
-foo
、onceToken
は nil (0 に初期化されることが保証されている statics によって保証されています) であり、ウェイターのリンクされたリストの先頭になるようにアトミックに変更されます。-foo
、スレッドは「2 番目の呼び出し元」と見なされ、この新しい下位スタック フレームに存在する待機構造がリストに追加され、セマフォで待機します。要するに、はい、あなたは行き詰まっています。ここでの実際的なポイントは、「dispatch_once
ブロックを再帰的に呼び出そうとしないこと」です。しかし、問題は間違いなく「無限再帰」ではなく、ブロックの実行が完了した後にフラグが変更されるだけではないことは間違いありません。終わる。
次のように、呼び出しがブロックの外側にあり、デッドロックが発生しないように、コードを少し変更できます。
- (void)foo
{
static dispatch_once_t onceToken;
BOOL shouldRunTwice = NO;
dispatch_once(&onceToken, ^{
shouldRunTwice = YES;
});
if (shouldRunTwice) {
[self foo];
}
// whatever...
}