2013-10-04 8 views
7

Neden kilitlenmiyorsunuz? Neden dispatch_once ile kilitleniyorum?

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

     [self foo]; 

    }); 

    // whatever... 
} 

ben foo ilk çağrıdan iki kez yürütülecek bekliyoruz.

+0

herhangi kırılma koşulu olmadan özyinelemeli yöntem gibi görünüyor yapmayın, ! – duDE

+1

neden foo'yu iki kez aramaya ihtiyacımız vardı? – manujmv

+0

NEDEN ÇÖZEĞİNİZ Arıyorsun özyinelemeli?!?!? – hfossli

cevap

22

Mevcut cevaplardan hiçbiri oldukça doğru değil (biri yanlış, diğeri biraz yanıltıcı ve bazı önemli ayrıntıları kaçırıyor). İlk olarak, right to the source gidelim:

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); 
    } 
} 

Yani gerçekten, diğer cevaplar aykırı olduğunu ne olur onceToken ilk arayan &dow yığını üzerinde bir adrese işaret etmek NULL arasında başlangıç ​​durumuna değiştirilir (çağrı bu arayan 1). Bu,'dan önce olur ve blok çağrılır. Eğer blok tamamlanmadan önce daha fazla kişi gelirse, blok tamamlanana kadar (0, arayanlar 2.n.N olarak adlandır), onceToken numaralı telefonun başındaki bağlantılı bir garson listesine eklenirler. Bu listeye eklendikten sonra, arayanlar 2..N, bloğun yürütülmesini tamamlamak için arayan 1 için bir semafor üzerinde bekleyiniz, bu noktada arayan 1, her arayan için 2. bir kez semaforu işaret eden bağlantılı listeyi yürüyecektir. Bu yürüyüşün başlangıcında, onceToken yeniden DISPATCH_ONCE_DONE (uygun bir işaretçi olamayacak bir değer olarak uygun şekilde tanımlanmıştır ve bu nedenle hiçbir zaman engellenen arayanların bağlantı listesinin başı olamaz) olarak değiştirilir. DISPATCH_ONCE_DONE, tamamlanmış durumu kontrol etmek için sonraki arayanlar için (sürecin geri kalanı boyunca) onu ucuz yapan şeydir. senin durumunda Yani

, neler olup bittiğine şudur:

, onceToken (Statik erdem 0'a başlatılması için garanti ediliyor tarafından garanti edilmektedir) nil Eğer -foo diyoruz ilk kez
  • ve atomik alır Bağlantılı garsonların başı olmak için değişti.
  • Blok içinden -foo özyinelemeyi aradığınızda, iş parçacığınızın "ikinci arayan" olarak kabul edilir ve bu yeni, alt yığın çerçevesinde bulunan bir garson yapısı listeye eklenir ve sonra beklersiniz semaforda.
  • Buradaki problem, bu semaforun hiçbir zaman sinyallenmeyeceğidir, çünkü sinyal verilebilmesi için, bloğunuzun bir çıkmaz nedeniyle gerçekleşemeyen (daha yüksek yığın çerçevesinde) çalışmasını bitirmesi gerekir.

Yani, kısacası, evet, çıkmaza ediyoruz ve burada pratik paket ise, "Bir dispatch_once bloğuna yinelemeli aramaya kalkmayın." Ama sorun DEĞİL "sonsuz özyinelemeler" kesinlikle, ve bayrak kesinlikle yürütme tamamlar bloktan sonra değişti sadece değil - blok onu arayanları yapmak bilir nasıl tam olduğunu yürütür değiştirmeden önce 2. Arayan 1'in bitmesini bekleyin.

2

aramalar bloğun dışındadır ve hiçbir kilitlenme, böyle bir şey var böylece, kod biraz değiştirebilir:

- (void)foo 
{ 
    static dispatch_once_t onceToken; 
    BOOL shouldRunTwice = NO; 
    dispatch_once(&onceToken, ^{ 
     shouldRunTwice = YES; 
    }); 
    if (shouldRunTwice) { 
     [self foo]; 
    } 
    // whatever... 
} 
İlgili konular