Parallel processing Julia-将GLPK.Prob发送给工人

Parallel processing Julia-将GLPK.Prob发送给工人,parallel-processing,julia,glpk,Parallel Processing,Julia,Glpk,我正在使用GLPK与朱莉娅,并使用斯宾塞里昂写的 sendto(2, lp = lp) #lp is type GLPK.Prob 但是,我似乎无法在工人之间发送GLPK.Prob类型。每当我尝试发送类型GLPK.Prob时,它就会被“发送”并调用 remotecall_fetch(2, whos) 确认GLPK.Prob已发送 当我试图通过打电话来解决问题时,问题出现了 simplex(lp) 错误 GLPK.GLPKError("invalid GLPK.Prob") 出现了。我知道

我正在使用GLPK与朱莉娅,并使用斯宾塞里昂写的

sendto(2, lp = lp) #lp is type GLPK.Prob
但是,我似乎无法在工人之间发送GLPK.Prob类型。每当我尝试发送类型GLPK.Prob时,它就会被“发送”并调用

remotecall_fetch(2, whos)
确认GLPK.Prob已发送

当我试图通过打电话来解决问题时,问题出现了

simplex(lp)
错误

GLPK.GLPKError("invalid GLPK.Prob")
出现了。我知道GLPK.Prob最初不是一个无效的GLPK.Prob,如果我决定在另一个worker(fx worker 2)上显式构造GLPK.Prob类型,调用simplex运行正常

这是一个问题,因为GLPK.Prob是从一个自定义类型的地雷生成的,它有点重

tl;dr是否存在无法在工作人员之间正确发送的某些类型

更新

function create_lp()
    lp = GLPK.Prob()

    GLPK.set_prob_name(lp, "sample")
    GLPK.term_out(GLPK.OFF)

    GLPK.set_obj_dir(lp, GLPK.MAX)

    GLPK.add_rows(lp, 3)
    GLPK.set_row_bnds(lp,1,GLPK.UP,0,100)
    GLPK.set_row_bnds(lp,2,GLPK.UP,0,600)
    GLPK.set_row_bnds(lp,3,GLPK.UP,0,300)

    GLPK.add_cols(lp, 3)

    GLPK.set_col_bnds(lp,1,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,1,10)
    GLPK.set_col_bnds(lp,2,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,2,6)
    GLPK.set_col_bnds(lp,3,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,3,4)

    s = spzeros(3,3)
    s[1,1] =  1
    s[1,2] =  1
    s[1,3] =  1
    s[2,1] =  10
    s[3,1] =  2
    s[2,2] =  4
    s[3,2] =  2
    s[2,3] =  5
    s[3,3] =  6

    GLPK.load_matrix(lp, s)

    return lp
end 
我现在明白了

remotecall_fetch(2, simplex, lp)
将返回上述GLPK错误

此外,我刚刚注意到GLPK模块有一个名为

GLPK.copy_prob(GLPK.Prob, GLPK.Prob, Int)
但是当复制一个GLPK.Prob时,deepcopy(当然不是copy)就不起作用了

示例

function create_lp()
    lp = GLPK.Prob()

    GLPK.set_prob_name(lp, "sample")
    GLPK.term_out(GLPK.OFF)

    GLPK.set_obj_dir(lp, GLPK.MAX)

    GLPK.add_rows(lp, 3)
    GLPK.set_row_bnds(lp,1,GLPK.UP,0,100)
    GLPK.set_row_bnds(lp,2,GLPK.UP,0,600)
    GLPK.set_row_bnds(lp,3,GLPK.UP,0,300)

    GLPK.add_cols(lp, 3)

    GLPK.set_col_bnds(lp,1,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,1,10)
    GLPK.set_col_bnds(lp,2,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,2,6)
    GLPK.set_col_bnds(lp,3,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,3,4)

    s = spzeros(3,3)
    s[1,1] =  1
    s[1,2] =  1
    s[1,3] =  1
    s[2,1] =  10
    s[3,1] =  2
    s[2,2] =  4
    s[3,2] =  2
    s[2,3] =  5
    s[3,3] =  6

    GLPK.load_matrix(lp, s)

    return lp
end 
这将返回一个lp::GLPK.Prob(),运行时将返回733.33

simplex(lp)
result = get_obj_val(lp)#returns 733.33
但是,

addprocs(1)
remotecall_fetch(2, simplex, lp)

将导致上述错误

问题似乎是您的
lp
对象包含指针

julia> lp = create_lp()
GLPK.Prob(Ptr{Void} @0x00007fa73b1eb330)
不幸的是,使用指针和并行处理是困难的——如果不同的进程有不同的内存空间,那么就不清楚进程应该查看哪个内存地址来访问指针指向的内存。这些问题是可以克服的,但显然,对于涉及上述指针的每种数据类型,它们都需要单独的工作,更多信息请参见GitHub讨论

因此,我的想法是,如果想要访问worker上的指针,可以在该worker上创建它。例如

using GLPK
addprocs(2)

@everywhere begin
    using GLPK
    function create_lp()
        lp = GLPK.Prob()

        GLPK.set_prob_name(lp, "sample")
        GLPK.term_out(GLPK.OFF)

        GLPK.set_obj_dir(lp, GLPK.MAX)

        GLPK.add_rows(lp, 3)
        GLPK.set_row_bnds(lp,1,GLPK.UP,0,100)
        GLPK.set_row_bnds(lp,2,GLPK.UP,0,600)
        GLPK.set_row_bnds(lp,3,GLPK.UP,0,300)

        GLPK.add_cols(lp, 3)

        GLPK.set_col_bnds(lp,1,GLPK.LO,0,0)
        GLPK.set_obj_coef(lp,1,10)
        GLPK.set_col_bnds(lp,2,GLPK.LO,0,0)
        GLPK.set_obj_coef(lp,2,6)
        GLPK.set_col_bnds(lp,3,GLPK.LO,0,0)
        GLPK.set_obj_coef(lp,3,4)

        s = spzeros(3,3)
        s[1,1] =  1
        s[1,2] =  1
        s[1,3] =  1
        s[2,1] =  10
        s[3,1] =  2
        s[2,2] =  4
        s[3,2] =  2
        s[2,3] =  5
        s[3,3] =  6

        GLPK.load_matrix(lp, s)

        return lp
    end 
end

a = @spawnat 2 eval(:(lp = create_lp()))
b = @spawnat 2 eval(:(result = simplex(lp)))
fetch(b)
有关使用它的更多信息,请参阅下面关于
@spawn
的文档,因为它可能需要一些时间来适应



@spawn
@spawnat
是Julia为工作人员分配任务而提供的两种工具。以下是一个例子:

julia> @spawnat 2 println("hello world")
RemoteRef{Channel{Any}}(2,1,3)

julia>  From worker 2:  hello world
这两个宏都将计算辅助进程上的。两者之间的唯一区别是
@spawnat
允许您选择哪个工作进程将计算表达式(在上面的示例中指定了工作进程2),而使用
@spawn
时,将根据可用性自动选择工作进程

在上面的示例中,我们只是让worker 2执行println函数。没有任何有趣的东西可以返回或从中检索。然而,我们发送给工人的表达式通常会产生我们希望检索的内容。请注意,在上面的示例中,当我们调用
@spawnat
时,在从worker 2获得打印输出之前,我们看到了以下内容:

RemoteRef{Channel{Any}}(2,1,3)
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
这表示
@spawnat
宏将返回
RemoteRef
类型对象。这个对象将依次包含从表达式中发送到worker的返回值。如果我们想要检索这些值,我们可以首先将
@spawnat
返回的
RemoteRef
分配给一个对象,然后使用
fetch()
函数对
RemoteRef
类型对象进行操作,以检索从对工作者执行的评估中存储的结果

julia> result = @spawnat 2 2 + 5
RemoteRef{Channel{Any}}(2,1,26)

julia> fetch(result)
7
有效使用
@spawn
的关键在于理解其操作的背后的本质。使用
@spawn
向工作程序发送命令比直接键入在其中一个工作程序上运行“解释器”或在其上本机执行代码时要键入的内容稍微复杂一些。例如,假设我们希望使用
@spawnat
为worker上的变量赋值。我们可以尝试:

@spawnat 2 a = 5
RemoteRef{Channel{Any}}(2,1,2)
它起作用了吗?好吧,让worker 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
错误消息表示未在辅助进程2上定义
a
。但这是为什么呢?原因是我们需要将赋值操作包装成一个表达式,然后使用
@spawn
告诉工作者进行求值。下面是一个示例,解释如下:

RemoteRef{Channel{Any}}(2,1,3)
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中使用
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
时,我们可以使用括号,这可能会使我们的语法更加清晰和易于管理。

您能发布一个可复制的示例吗?谢谢,这接近于我最后所做的。我从lp中获取每个相关数组并将其传输过来,并在创建lp的每个工作进程上定义一个create_lp()方法。我仍然希望有一种方法可以使用copy_prob方法,并且能够将指针(如果您可以这样做的话)移动到另一个worker,因为搜索总是存在的elegance@isebarn当然可以另外,请注意,当您使用
remotecall\u fetch()
然后向函数提供参数时,默认情况下,您指定的对象将来自调用
remotecall\u fetch()
的进程的范围,然后这些对象将被发送到正在激活的进程,而不是在工作范围内以本机方式使用参数进行远程调用