Entsprechend der Dokumentation unter [email protected]
, "@async
umschließt einen Ausdruck in einer Aufgabe." Was das bedeutet, ist, dass Julia, egal was in ihren Bereich fällt, diese Aufgabe startet, aber dann weitergeht zu dem, was als nächstes im Skript kommt, ohne auf den Abschluss der Aufgabe zu warten. So zum Beispiel, ohne das Makro erhalten Sie:
julia> @time sleep(2)
2.005766 seconds (13 allocations: 624 bytes)
Aber mit dem Makro, erhalten Sie:
julia> @time @async sleep(2)
0.000021 seconds (7 allocations: 657 bytes)
Task (waiting) @0x0000000112a65ba0
julia>
Julia ermöglicht somit das Skript zu gehen (und das @time
Makro vollständig auszuführen) ohne auf die Aufgabe zu warten (in diesem Fall für zwei Sekunden zu schlafen).
Das @sync
Makro, dagegen wird „Warten Sie, bis alle dynamisch geschlossenen Verwendungen von @async
, @spawn
, @spawnat
und @parallel
abgeschlossen sind.“ (laut Dokumentation unter [email protected]
). So sehen wir:
julia> @time @sync @async sleep(2)
2.002899 seconds (47 allocations: 2.986 KB)
Task (done) @0x0000000112bd2e00
In diesem einfachen Beispiel dann gibt es keinen Punkt ist eine einzelne Instanz @async
und @sync
zusammen einschließlich. Aber wo @sync
kann nützlich sein, wo Sie haben @async
für mehrere Operationen, die Sie alle gleichzeitig starten möchten, ohne zu warten, dass jeder abgeschlossen.
Angenommen, wir haben mehrere Arbeiter und möchten jeden von ihnen gleichzeitig an einer Aufgabe arbeiten lassen und dann die Ergebnisse dieser Aufgaben abrufen. Ein erster (aber falsch) Versuch könnte sein:
addprocs(2)
@time begin
a = cell(nworkers())
for (idx, pid) in enumerate(workers())
a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
## 4.011576 seconds (177 allocations: 9.734 KB)
Das Problem hierbei ist, dass die Schleife für jeden remotecall_fetch() Operation wartet, für jeden Prozess seiner Arbeit abzuschließen, dh zu beenden (in diesem Fall für 2 Sekunden Schlaf) bevor mit dem nächsten remotecall_fetch() - Vorgang fortgefahren wird. In Bezug auf eine praktische Situation erhalten wir hier nicht die Vorteile der Parallelität, da unsere Prozesse nicht gleichzeitig ihre Arbeit verrichten (d. H. Schlafen).
Wir dies korrigieren kann jedoch durch eine Kombination der @async
und @sync
Makros:
@time begin
a = cell(nworkers())
@sync for (idx, pid) in enumerate(workers())
@async a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
## 2.009416 seconds (274 allocations: 25.592 KB)
Nun, wenn wir jeden Schritt der Schleife als separater Betrieb zählen, sehen wir, dass es zwei separate Operationen, denen das Makro @async
vorausgeht. Das Makro ermöglicht, dass jedes von diesen gestartet wird, und der Code wird fortgesetzt (in diesem Fall zum nächsten Schritt der Schleife), bevor jeder beendet ist.Aber die Verwendung des Makros @sync
, dessen Gültigkeitsbereich die gesamte Schleife umfasst, bedeutet, dass das Skript nicht über diese Schleife hinausgehen darf, bis alle Operationen abgeschlossen sind, denen @async
vorausgegangen ist.
Es ist möglich, ein noch klareres Verständnis der Funktionsweise dieser Makros zu erhalten, indem man das obige Beispiel weiter optimiert, um zu sehen, wie es sich unter bestimmten Modifikationen ändert. Zum Beispiel: Angenommen wir haben nur die @async
ohne @sync
:
@time begin
a = cell(nworkers())
for (idx, pid) in enumerate(workers())
println("sending work to $pid")
@async a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
## 0.001429 seconds (27 allocations: 2.234 KB)
Hier wird das @async
Makro ermöglicht es uns, in unserer Schleife fortzusetzen, selbst vor jeder remotecall_fetch() Operation beendet haben sollte. Aber zum Besseren oder Schlechteren haben wir kein Makro @sync
, um zu verhindern, dass der Code über diese Schleife hinausgeht, bis alle remotecall_fetch() -Operationen abgeschlossen sind.
Nichtsdestotrotz läuft jede remotecall_fetch() Operation noch parallel, auch wenn wir weitermachen. Wir können sehen, dass, weil, wenn wir für zwei Sekunden warten, dann das Array ein, die Ergebnisse enthalten, beinhalten:
sleep(2)
julia> a
2-element Array{Any,1}:
nothing
nothing
(Das „Nichts“ Element das Ergebnis eines erfolgreich ist von den Ergebnissen der Schlaf holen (Funktion, die keine Werte zurückgibt)
Wir können auch sehen, dass die zwei remotecall_fetch() Operationen im Wesentlichen zur gleichen Zeit starten, weil die Druckbefehle, die ihnen vorausgehen, auch in schneller Folge ausgeführt werden (Ausgabe von diesen Befehlen hier nicht gezeigt)). Vergleichen Sie dies mit dem nächsten Beispiel, wo die Druckbefehle mit einer Verzögerung von 2 Sekunden ausgeführt werden:
Wenn wir das Makro @async
auf die ganze Schleife setzen (statt nur den inneren Schritt davon), dann wird unser Skript wieder Fahren Sie sofort fort, ohne zu warten, bis die remotecall_fetch() -Operationen abgeschlossen sind. Jetzt erlauben wir jedoch nur, dass das Skript als Ganzes über die Schleife hinausgeht. Wir lassen nicht zu, dass jeder einzelne Schritt der Schleife beginnt, bevor der vorherige abgeschlossen ist. Daher gibt es, anders als im obigen Beispiel, zwei Sekunden, nachdem das Skript nach der Schleife fortschreitet, das Ergebnisarray, das immer noch ein Element als #undef anzeigt, das anzeigt, dass die zweite remotecall_fetch() -Operation noch nicht abgeschlossen ist.
@time begin
a = cell(nworkers())
@async for (idx, pid) in enumerate(workers())
println("sending work to $pid")
a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
# 0.001279 seconds (328 allocations: 21.354 KB)
# Task (waiting) @0x0000000115ec9120
## This also allows us to continue to
sleep(2)
a
2-element Array{Any,1}:
nothing
#undef
Und, nicht überraschend, wenn wir setzen die @sync
und @async
direkt nebeneinander, wir bekommen, dass jede remotecall_fetch() läuft nacheinander (nicht gleichzeitig), aber wir nicht weiter in den Code, bis jeder hat beendet. Mit anderen Worten, wäre dies, glaube ich, im Wesentlichen entspricht, wenn wir weder Makro an Ort und Stelle hatten, genau wie sleep(2)
zu @sync @async sleep(2)
@time begin
a = cell(nworkers())
@sync @async for (idx, pid) in enumerate(workers())
a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
# 4.019500 seconds (4.20 k allocations: 216.964 KB)
# Task (done) @0x0000000115e52a10
Hinweis im Wesentlichen identisch verhält sich auch, dass es möglich ist, kompliziertere Operationen nach innen haben der Umfang der @async
Makro. Die documentation gibt ein Beispiel, das eine gesamte Schleife innerhalb des Umfangs von @async
enthält.
Update: „, bis alle dynamisch geschlossenen Anwendungen von @async
, Warten @spawn
, @spawnat
und @parallel
sind abgeschlossen“ Recall, dass die Hilfe für die Sync-Makros besagt, dass es Für die Zwecke dessen, was als "vollständig" gilt, ist es wichtig, wie Sie die Aufgaben im Rahmen der Makros @sync
und @async
definieren.Betrachten Sie das folgende Beispiel, die eine leichte Variation oben auf eines der Beispiele ist gegeben:
@time begin
a = cell(nworkers())
@sync for (idx, pid) in enumerate(workers())
@async a[idx] = remotecall(pid, sleep, 2)
end
end
## 0.172479 seconds (93.42 k allocations: 3.900 MB)
julia> a
2-element Array{Any,1}:
RemoteRef{Channel{Any}}(2,1,3)
RemoteRef{Channel{Any}}(3,1,4)
Das frühere Beispiel nahm etwa 2 Sekunden ausgeführt werden, was darauf hinweist, dass die beiden Aufgaben parallel ausgeführt wurden und dass das Skript warten jeweils um die Ausführung ihrer Funktionen abzuschließen, bevor sie fortfahren. Dieses Beispiel hat jedoch eine viel geringere Zeitauswertung. Der Grund dafür ist, dass für den Zweck von @sync
die remotecall() - Operation "beendet" ist, sobald sie dem Arbeiter den Job gesendet hat. (Beachten Sie, dass das resultierende Array, a, hier nur RemoteRef-Objekttypen enthält, die lediglich anzeigen, dass bei einem bestimmten Prozess etwas im Gange ist, das theoretisch zu irgendeinem Zeitpunkt in der Zukunft abgerufen werden könnte). Im Gegensatz dazu hat die remotecall_fetch() Operation nur "beendet", wenn sie die Nachricht vom Worker erhält, dass ihre Aufgabe abgeschlossen ist.
So, wenn Sie nach Möglichkeiten suchen, sicherzustellen, dass bestimmte Operationen mit Arbeitern abgeschlossen haben, bevor Sie in Ihrem Skript weitermachen (wie zum Beispiel in diesem Post diskutiert: Waiting for a task to be completed on remote processor in Julia) ist es notwendig, sorgfältig darüber nachzudenken, was zählt als " vervollständigen "und wie Sie das in Ihrem Skript messen und dann operationalisieren.
Dieser Beitrag wurde von den hilfreichen Antworten und Diskussionen von @FelipeLema in diesem Beitrag inspiriert: http://StackOverflow.com/Questions/32143159/waiting-for-a-task-to-be-completed-on-remote- Prozessor-in-Julia/32148849 # 32148849 –
Eine schöne Antwort! – StefanKarpinski