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.
herhangi kırılma koşulu olmadan özyinelemeli yöntem gibi görünüyor yapmayın, ! – duDE
neden foo'yu iki kez aramaya ihtiyacımız vardı? – manujmv
NEDEN ÇÖZEĞİNİZ Arıyorsun özyinelemeli?!?!? – hfossli