2013-10-04 16 views
7

मैं डेडलॉकिंग क्यों कर रहा हूं?मुझे dispatch_once के साथ डेडलॉक क्यों मिल रहा है?

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

     [self foo]; 

    }); 

    // whatever... 
} 

मैं foo पहली कॉल पर दो बार निष्पादित करने के लिए उम्मीद है।

+0

किसी भी तोड़ शर्त के बिना एक पुनरावर्ती विधि के लिए लगता है, यह मत करो ! – duDE

+1

आपको दो बार फू कॉल करने की आवश्यकता क्यों है? – manujmv

+0

क्यों आप इसे रिकर्सिव कॉल करना चाहते हैं?!?! – hfossli

उत्तर

22

मौजूदा उत्तरों में से कोई भी सटीक सटीक नहीं है (कोई गलत है, दूसरा थोड़ा भ्रामक है और कुछ महत्वपूर्ण विवरण याद करता है)। सबसे पहले, के 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)। यह ब्लॉक से पहले होता है। यदि ब्लॉक पूरा होने से पहले अधिक कॉलर पहुंचते हैं, तो वे वेटर्स की एक लिंक्ड सूची में जोड़े जाते हैं, जिनमें से प्रमुख onceToken में निहित है जब तक कि ब्लॉक पूरा नहीं होता है (कॉलर्स 2. कॉल करें)। इस सूची में जोड़े जाने के बाद, कॉलर्स 2..N ब्लॉक के निष्पादन को पूरा करने के लिए कॉलर 1 के लिए सेमफोर पर प्रतीक्षा करें, जिस बिंदु पर कॉलर 1 प्रत्येक कॉलर के लिए एक बार सैमफोर को सिग्नल करने वाली लिंक्ड सूची चलाएगा। एन। उस चलने की शुरुआत में, onceToken बदल दिया गया है फिरDISPATCH_ONCE_DONE (जिसे आसानी से एक मान माना जाता है जो वैध सूचक कभी नहीं हो सकता है, और इसलिए अवरुद्ध कॉलर्स की एक लिंक की गई सूची का प्रमुख कभी नहीं हो सकता है।) बदलना यह DISPATCH_ONCE_DONE है जो पूर्ण कॉल की जांच करने के लिए बाद के कॉलर्स (प्रक्रिया के बाकी जीवनकाल के लिए) के लिए सस्ता बनाता है।

अपने मामले में

तो, क्या हो रहा है यह है:

  • पहली बार जब आप -foo फोन, onceToken शून्य (जिसके द्वारा स्टैटिक्स के आधार पर 0 के लिए शुरू किए जाने की गारंटी की जा रही गारंटी है) है, और atomically हो जाता है वेटर्स की लिंक्ड सूची का प्रमुख बन गया।
  • जब आप ब्लॉक के अंदर से -foo को दोबारा कॉल करते हैं, तो आपका धागा "दूसरा कॉलर" और एक वेटर संरचना माना जाता है, जो इस नए, निचले ढेर फ्रेम में मौजूद है, सूची में जोड़ा जाता है और फिर आप प्रतीक्षा करने के लिए जाते हैं सेमफोर पर।
  • समस्या यह है कि इस सेमफोर को कभी संकेत नहीं दिया जाएगा क्योंकि इसे संकेतित करने के लिए, आपके ब्लॉक को निष्पादन (उच्च ढेर फ्रेम में) समाप्त करना होगा, जो अब डेडलॉक के कारण नहीं हो सकता है।

तो, संक्षेप में, हाँ, आप भी गतिरोध रहे हैं, और यहां व्यावहारिक टेकअवे है, "एक 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... 
} 
संबंधित मुद्दे