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