Keine der vorhandenen Antworten ist ziemlich genau (eine ist völlig falsch, die andere ist ein bisschen irreführend und vermisst einige kritische Details). Zunächst gehen wir direkt zur Quelle :
%Vor% Was wirklich passiert, ist, dass onceToken
im Gegensatz zu den anderen Antworten von seinem Anfangszustand NULL
auf eine Adresse auf dem Stack des ersten Aufrufers &dow
(Aufruf dieses Aufrufers 1) geändert wird. . Dies geschieht bevor der Block aufgerufen wird. Wenn mehr Anrufer ankommen, bevor die Blockierung abgeschlossen ist, werden sie zu einer verketteten Liste von Kellnern hinzugefügt, deren Kopf in onceToken
enthalten ist, bis der Block abgeschlossen ist (Anrufer anrufen 2..N). Nach dem Hinzufügen zu dieser Liste warten Anrufer 2..N auf einen Semaphor für Anrufer 1, um die Ausführung des Blocks abzuschließen, wobei Anrufer 1 die verkettete Liste durchlaufen wird, die den Semaphor einmal für jeden Anrufer 2..N signalisiert. Zu Beginn dieses Durchlaufs wird onceToken
erneut geändert, um DISPATCH_ONCE_DONE
zu sein (was zweckmäßigerweise als ein Wert definiert ist, der niemals ein gültiger Zeiger sein könnte und daher niemals der Kopf von sein könnte eine verkettete Liste geblockter Anrufer.) Wenn Sie sie in DISPATCH_ONCE_DONE
ändern, ist es für nachfolgende Anrufer (für den Rest der Lebensdauer des Prozesses) günstig, den abgeschlossenen Status zu überprüfen.
In Ihrem Fall ist das also:
-foo
zum ersten Mal aufrufen, ist onceToken
null (was garantiert ist, weil die Statik garantiert auf 0 initialisiert wird) und wird atomar geändert, um zum Kopf der verknüpften Liste von Kellnern zu werden. -foo
rekursiv aus dem Block heraus aufrufen, wird Ihr Thread als "zweiter Aufrufer" betrachtet und eine Kellnerstruktur, die in diesem neuen, niedrigeren Stapelrahmen existiert, wird zur Liste hinzugefügt und Sie gehen auf den Semaphor warten. Kurz gesagt, ja, Sie sind festgefahren, und das praktische Mitnehmen hier ist: "Versuchen Sie nicht, rekursiv in einen dispatch_once
-Block zu rufen." Aber das Problem ist definitiv NICHT "unendliche Rekursion", und das Flag wird definitiv nicht nur geändert, nachdem der Block die Ausführung beendet hat - vor Der Block wird ausgeführt, genau wie er Caller 2.N kennen kann. Warten Sie, bis der Aufrufer 1 beendet ist.
Sie könnten den Code ein wenig ändern, so dass die Aufrufe außerhalb des Blocks liegen und es kein Deadlock gibt, etwa so:
%Vor%Tags und Links objective-c ios grand-central-dispatch macos semaphore