我想在运行时获取任意函数,该函数可能使用或不使用
@everywhere
宏定义,并以分布式方式运行它。
我的第一个天真的尝试是简单地尝试使用
pmap
内部的函数
@assert nprocs() > 1
function g(f)
pmap(1:5) do x
f(x)
end
end
addone(x) = x+1
subone(x) = x-1
g(addone)
g(subone)
当然,这并没有奏效并导致了。
On worker 2:
UndefVarError: `#addone` not defined
接下来我尝试将函数 f 作为 pmap 的参数传递
@assert nprocs() > 1
function g(f)
pmap(zip(1:5, Iterators.repeated(f))) do (x,f)
f(x)
end
end
addone(x) = x+1
subone(x) = x-1
g(addone)
g(subone)
这个也没用,还扔了
On worker 2:
UndefVarError: `#addone` not defined
现在我很茫然,这样的事情在 Julia 中肯定是可能的。
这是可能的,但你不应该这样做,因为这很容易导致工作量太大。
从根本上来说,问题是一个方法只存在于您想要调用它的多个进程中的一个上。正确的做法是在实例化该方法的源代码处放置
@everywhere
来预防;根据方法的需要,这可能位于 function
块本身、运行文件的 include
调用或包的 using
/import
。请记住,作为一个宏,@everywhere
不会获取现有实例并将其复制到多个进程中,它只是在每个进程中计算以下源代码表达式。
也就是说,可以从函数实例和参数派生方法的表达式实例(CodeTracking.jl 使这变得容易),并且可以使用
@eval
和 @everywhere
在其他进程上对其进行评估。这在您的简单示例中是可行的,但通常比这更复杂。方法定义本身不会复制它定义的名称空间或记住它是如何评估的,因此您需要单独派生相关模块或全局变量的表达式(据我所知,没有包可以让这变得容易)。正确地解决这个问题比前面提到的预防要困难得多,也更混乱。
@BatWannBee 完全正确,你不应该这样做,应该只使用
@everywhere
。
但是,如果您想这样做,这里是代码片段。
首先我们进行设置
using Distributed
addprocs(2)
@everywhere using Serialization
addone(x::Int) = x+1 + 100myid()
现在我们将这个功能转移给其他工人
# the name of the function to be moved around
fname = :addone
# Serializing methods of the function fname to a buffer
buf = IOBuffer()
serialize(buf, methods(eval(fname)))
# Deserializing the function on remote workers
# Note that there are two steps
# 1. creating an empty function
# 2. providing methods
Distributed.remotecall_eval(Main, workers(), quote
function $fname end
deserialize(seekstart($buf))
end)
现在我们可以测试我们所做的事情:
julia> fetch(@spawnat 3 methods(addone))
# 1 method for generic function "addone" from Main:
[1] addone(x::Int64)
@ REPL[3]:1
julia> fetch(@spawnat 3 addone(4))
305