Mic*_*gge 23 parallel-processing macros synchronization asynchronous julia
我已经阅读了和宏的文档,但仍然无法弄清楚如何以及何时使用它们,也无法在互联网上的其他地方找到许多资源或示例. @async@sync
我的直接目标是找到一种方法来设置几个工作程序并行工作,然后等待它们全部完成后继续我的代码.这篇文章:等待在Julia的远程处理器上完成的任务包含一个成功的方法来实现这一点.我原以为应该可以使用@async和@sync宏,但是我最初的失败让我想知道我是否正确理解了如何以及何时使用这些宏.
Mic*_*gge 47
根据下面的文档?@async," @async在一个Task中包装一个表达式".这意味着,对于任何属于其范围的内容,Julia将开始运行此任务,然后继续执行脚本中的下一步,而无需等待任务完成.因此,例如,如果没有宏,您将得到:
julia> @time sleep(2)
2.005766 seconds (13 allocations: 624 bytes)
Run Code Online (Sandbox Code Playgroud)
但是使用宏,你得到:
julia> @time @async sleep(2)
0.000021 seconds (7 allocations: 657 bytes)
Task (waiting) @0x0000000112a65ba0
julia>
Run Code Online (Sandbox Code Playgroud)
因此,Julia允许脚本继续(并且@time宏完全执行),而无需等待任务(在这种情况下,休眠两秒钟)完成.
该@sync宏,相比之下,将"等到所有动态封闭使用@async,@spawn,@spawnat和@parallel是完整的." (根据下面的文件?@sync).因此,我们看到:
julia> @time @sync @async sleep(2)
2.002899 seconds (47 allocations: 2.986 KB)
Task (done) @0x0000000112bd2e00
Run Code Online (Sandbox Code Playgroud)
在这个简单的例子中,没有必要包括单个@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)
Run Code Online (Sandbox Code Playgroud)
这里的问题是循环等待每个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)
Run Code Online (Sandbox Code Playgroud)
现在,如果我们将循环的每一步都计算为一个单独的操作,我们会看到在@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)
Run Code Online (Sandbox Code Playgroud)
在这里,@async宏允许我们在每个remotecall_fetch()操作完成执行之前继续循环.但是,无论好坏,我们都没有@sync宏来阻止代码继续经过这个循环,直到所有remotecall_fetch()操作完成.
尽管如此,remotecall_fetch()即使我们继续,每个操作仍然会并行运行.我们可以看到,因为如果我们等待两秒钟,那么包含结果的数组a将包含:
sleep(2)
julia> a
2-element Array{Any,1}:
nothing
nothing
Run Code Online (Sandbox Code Playgroud)
("nothing"元素是成功获取sleep函数结果的结果,该函数不返回任何值)
我们还可以看到这两个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
Run Code Online (Sandbox Code Playgroud)
并且,毫不奇怪,如果我们将the @sync和@asyncright 放在彼此旁边,我们得到的是每个都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
Run Code Online (Sandbox Code Playgroud)
另请注意,@async宏可以在宏的范围内进行更复杂的操作.该文档提供了一个示例,其中包含一个完整的循环@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)
Run Code Online (Sandbox Code Playgroud)
前面的示例大约需要2秒钟才能执行,表明这两个任务是并行运行的,并且脚本在继续之前等待每个任务完成其功能的执行.然而,这个例子的评估时间要低得多.其原因在于为宗旨@sync的remotecall()操作已"完成",一旦发送了工人的工作要做.(请注意,结果数组a在这里只包含RemoteRef对象类型,它们只表示特定进程正在发生某些事情,理论上可以在将来的某个时刻获取).相比之下,当remotecall_fetch()操作从工作人员获得其任务完成的消息时,操作才"完成".
因此,如果您正在寻找方法来确保在继续执行脚本之前已完成对工作程序的某些操作(例如,在本文中讨论:等待在Julia中的远程处理器上完成任务),有必要仔细考虑什么算作"完整",以及如何在脚本中测量并操作它.