产卵和产卵
宏 @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
时我们可以使用括号,在这种情况下,这可能会使我们的语法更清晰和易于管理。