16

我已閱讀documentation中的@async@sync宏,但仍無法弄清楚如何以及何時使用它們,我也無法在其他地方找到許多資源或示例互聯網。如何以及何時在Julia中使用@async和@sync

我的目標是找到一種方法來設置幾個工作人員並行工作,然後等待他們完成了所有工作,然後繼續執行我的代碼。這篇文章:Waiting for a task to be completed on remote processor in Julia包含一個成功的方法來實現這一點。我曾認爲應該可以使用@async@sync宏,但是我最初的失敗使我不知道我是否正確理解如何以及何時使用這些宏。

回答

32

根據[email protected]下的文檔,「@async在任務中包裝表達式」。這意味着,對於任何屬於其範圍內的內容,Julia都會開始執行此任務,然後繼續處理腳本中的下一個任務,而無需等待任務完成。因此,舉例來說,如果沒有宏觀您將獲得:

julia> @time sleep(2) 
    2.005766 seconds (13 allocations: 624 bytes) 

但隨着宏,你會得到:

julia> @time @async sleep(2) 
    0.000021 seconds (7 allocations: 657 bytes) 
Task (waiting) @0x0000000112a65ba0 

julia> 

朱莉婭從而允許腳本繼續(與@time宏完全執行)無需等待任務(在這種情況下,睡眠兩秒鐘)即可完成。

@sync宏,相比之下,將「等到所有動態封閉的@async@spawn@spawnat@parallel用途是完整的。」 (根據[email protected]下的文件)。於是,我們看到:

julia> @time @sync @async sleep(2) 
    2.002899 seconds (47 allocations: 2.986 KB) 
Task (done) @0x0000000112bd2e00 

在這個簡單的例子然後,沒有指向包括@async@sync單個實例在一起。但是,在@sync可用的情況下,您可以將@async應用於您希望允許所有人一次啓動而不等待每個完成的多個操作。

例如,假設我們有多個工作人員,我們希望他們每個人都同時開展一項任務,然後從這些任務中獲取結果。最初的(但不正確的)嘗試可能是:

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) 

這裏的問題是,循環等待每個remotecall_fetch()操作完成,即對於每一個進程完成其工作(在這種情況下睡2秒),然後繼續開始下一個remotecall_fetch()操作。就實際情況而言,我們並沒有從這裏獲得並行的好處,因爲我們的流程並沒有同時工作(即睡眠)。

我們可以但是解決這個問題,通過使用@async@sync宏的組合:現在

@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) 

,如果再算上循環作爲一個單獨操作的每一個步驟,我們可以看到,有兩個以@async之前的宏進行單獨的操作。宏允許每個啓動,並且代碼在每個完成之前繼續執行(在本例中爲循環的下一步)。但是,使用範圍包含整個循環的@sync宏意味着我們將不允許腳本繼續循環,直到所有以@async開頭的操作都完成爲止。

通過進一步調整上述示例以瞭解它在某些修改下如何變化,可以更清楚地瞭解這些宏的操作。舉例來說,假設我們只是有@async沒有@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) 

這裏,@async宏允許我們甚至執行完畢之前,各remotecall_fetch()操作在我們的循環繼續。但是,無論好壞,我們沒有@sync宏,以防止代碼繼續經過這個循環,直到所有remotecall_fetch()操作完成。儘管如此,即使我們繼續,每個remotecall_fetch()操作仍然在並行運行。我們可以看到,因爲如果我們等待2秒鐘,然後陣列,包含結果,將包含:

sleep(2) 
julia> a 
2-element Array{Any,1}: 
nothing 
nothing 

(在「無」元素是一個成功的結果取睡眠的結果函數,它不返回任何值)

我們還可以看到,兩個remotecall_fetch()操作基本上同時啓動,因爲它們之前的打印命令也是快速連續執行的(這些命令的輸出未在此處顯示)。與下一個例子中的打印命令以相差2秒的延遲進行對比:

如果我們將@async宏放在整個循環(而不是它的內部步驟)上,那麼我們的腳本將再次立即繼續而不等待remotecall_fetch()操作完成。但是,現在我們只允許腳本作爲一個整體繼續循環。我們不允許循環的每個單獨步驟在前一個完成之前啓動。因此,與上面的示例不同,腳本在循環之後兩秒鐘後,結果數組仍然有一個元素作爲#undef,指示第二個remotecall_fetch()操作仍未完成。

@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  

而且,這並不奇怪,如果我們把@sync@async緊挨着對方,我們得到了各remotecall_fetch()順序(而非同時)運行,但我們不代碼,直到每一個繼續已完成。換句話說,這將是,我認爲,基本上,如果我們在地方既無宏觀,就像sleep(2)相當於行爲基本上等同於@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 

還要注意的是,可以有內部更復雜的操作@async宏的範圍。 documentation給出了一個示例,其中包含@async範圍內的整個循環。

更新:回想一下,對於同步宏的幫助指出,它將「等到@async@spawn@spawnat@parallel完成所有動態封閉的用途。」出於「完整」的目的,重要的是如何在@sync@async宏的範圍內定義任務。考慮下面的例子,這是上面給出的實施例之一的輕微變化:

@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) 

前面的示例大約花費2秒執行,這表明這兩個任務是在並行運行和腳本等待每個人在繼續之前完成其功能的執行。然而,這個例子的評估時間要少得多。原因在於,爲了達到@sync的目的,remotecall()操作在它向該工作人員發送該作業後「完成」。 (請注意,結果數組a僅包含RemoteRef對象類型,它們只是表明某個特定進程正在發生某些進程,理論上這些進程可能在將來的某個時間點進行提取)。相比之下,remotecall_fetch()操作在從任務完成的工作人員處收到消息時纔會「完成」。

因此,如果您正在尋找方法以確保在您的腳本中繼續進行某些與工作人員的操作之前(例如在本文中討論:Waiting for a task to be completed on remote processor in Julia),有必要仔細考慮什麼算作「完成「以及如何測量並在腳本中操作。

+2

這篇文章的靈感來自於@FelipeLema在這篇文章中的有用答案和討論:http://stackoverflow.com/questions/32143159/waiting-for-a-task-to-be-completed-on-remote-處理器在茱莉亞/ 32148849#32148849 –

+7

一個可愛的答案! – StefanKarpinski

相關問題