產卵和產卵
巨集 @spawn
和 @spawnat
是 Julia 提供給工人分配任務的兩個工具。這是一個例子:
julia> @spawnat 2 println("hello world")
RemoteRef{Channel{Any}}(2,1,3)
julia> From worker 2: hello world
這兩個巨集都將評估工作程序上的表示式 。兩者之間的唯一區別是 @spawnat
允許你選擇哪個工作人員將評估表示式(在上面的示例中指定了 worker 2),而使用 @spawn
,將根據可用性自動選擇工作人員。
在上面的例子中,我們只是讓 worker 2 執行 println 函式。沒有興趣返回或從中檢索。但是,通常,我們傳送給工作人員的表示式將產生我們希望檢索的內容。請注意,在上面的例子中,當我們呼叫 @spawnat
時,在從 worker 2 獲得列印輸出之前,我們看到了以下內容:
RemoteRef{Channel{Any}}(2,1,3)
這表明 @spawnat
巨集將返回 RemoteRef
型別的物件。該物件將包含傳送給 worker 的表示式的返回值。如果我們想要檢索這些值,我們可以首先分配 @spawnat
返回到物件的 RemoteRef
,然後使用對 RemoteRef
型別物件進行操作的 fetch()
函式來檢索從對 worker 執行的評估所儲存的結果。
julia> result = @spawnat 2 2 + 5
RemoteRef{Channel{Any}}(2,1,26)
julia> fetch(result)
7
能夠有效使用 @spawn
的關鍵是理解它所執行的表示式背後的本質。使用 @spawn
向工作人員傳送命令比直接鍵入你在其中一個工作者上執行直譯器或在其上執行程式碼時直接鍵入的內容要複雜得多。例如,假設我們希望使用 @spawnat
為工作者的變數賦值。我們可能會嘗試:
@spawnat 2 a = 5
RemoteRef{Channel{Any}}(2,1,2)
它有用嗎?好吧,讓我們看看工人 2 試圖列印 a
。
julia> @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,4)
julia>
沒啥事兒。為什麼?我們可以通過使用如上所述的 fetch()
來進行更多研究。fetch()
非常方便,因為它不僅可以檢索成功的結果,還可以檢索錯誤訊息。沒有它,我們甚至可能不知道出了什麼問題。
julia> result = @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,5)
julia> fetch(result)
ERROR: On worker 2:
UndefVarError: a not defined
錯誤訊息說 a
沒有在 worker 2 上定義。但為什麼會這樣?原因是我們需要將賦值操作包裝到一個表示式中,然後我們使用 @spawn
來告訴 worker 進行求值。以下是一個示例,說明如下:
julia> @spawnat 2 eval(:(a = 2))
RemoteRef{Channel{Any}}(2,1,7)
julia> @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,8)
julia> From worker 2: 2
:()
語法是 Julia 用來指定表示式的語法。然後我們在 Julia 中使用 eval()
函式來計算表示式,我們使用 @spawnat
巨集來指示在 worker 2 上計算表示式。
我們也可以獲得與以下相同的結果:
julia> @spawnat(2, eval(parse("c = 5")))
RemoteRef{Channel{Any}}(2,1,9)
julia> @spawnat 2 println(c)
RemoteRef{Channel{Any}}(2,1,10)
julia> From worker 2: 5
此示例演示了另外兩個概念。首先,我們看到我們也可以使用在字串上呼叫的 parse()
函式建立表示式。其次,我們看到在呼叫 @spawnat
時我們可以使用括號,在這種情況下,這可能會使我們的語法更清晰和易於管理。