2013-10-04 19 views
7

なぜ私はデッドロックですか?なぜdispatch_onceでデッドロックが発生するのですか?

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

     [self foo]; 

    }); 

    // whatever... 
} 

最初の呼び出しでfooが2回実行されると思います。

+0

は、任意のブレーク条件なしで再帰的方法であるように思わ! – duDE

+1

なぜfooを2回呼び出す必要がありましたか? – manujmv

+0

なぜあなたはそれを再帰的に呼びたいのですか?!?!? – hfossli

答えて

22

既存の回答は全く正確ではありません(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が終了するのを待つ。

2

あなたが呼び出しはブロックの外側で、デッドロックがありませんように、このような何か、コードを少し変更することができます:それをしない、

- (void)foo 
{ 
    static dispatch_once_t onceToken; 
    BOOL shouldRunTwice = NO; 
    dispatch_once(&onceToken, ^{ 
     shouldRunTwice = YES; 
    }); 
    if (shouldRunTwice) { 
     [self foo]; 
    } 
    // whatever... 
} 
関連する問題