我已经阅读了和宏的文档,但仍然无法弄清楚如何以及何时使用它们,也无法在Internet上的其他地方找到许多关于它们的资源或示例。@async
@sync
我的近期目标是找到一种方法,让多个工作人员并行进行工作,然后等到他们全部完成我的代码后再进行工作。这篇文章:等待在Julia中的远程处理器上完成任务包含一种成功的方法。我曾以为可以使用@async
和@sync
宏,但最初的失败使我想知道自己是否正确理解了如何以及何时使用这些宏。
根据下的文档?@async
,“ @async
将表达式包装在Task中”。这意味着,对于处于其范围内的任何内容,Julia都会开始运行此任务,然后继续执行脚本中的后续操作,而无需等待任务完成。因此,例如,没有宏,您将得到:
julia> @time sleep(2)
2.005766 seconds (13 allocations: 624 bytes)
但是使用宏,您将获得:
julia> @time @async sleep(2)
0.000021 seconds (7 allocations: 657 bytes)
Task (waiting) @0x0000000112a65ba0
julia>
因此,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
那么,在这个简单的示例中,没有必要将@async
and 的单个实例包括在内@sync
。但是,@sync
有用的地方@async
是您希望将多个操作应用于所有操作,这些操作可以一次全部开始,而不必等待每个操作完成。
例如,假设我们有多个工作人员,并且我们想开始让他们每个人同时处理一个任务,然后从这些任务中获取结果。最初(但不正确)的尝试可能是:
using Distributed
cell(N) = Vector{Any}(undef, N)
addprocs(2)
@time begin
a = cell(nworkers())
for (idx, pid) in enumerate(workers())
a[idx] = remotecall_fetch(sleep, pid, 2)
end
end
## 4.011576 seconds (177 allocations: 9.734 KB)
这里的问题是循环remotecall_fetch()
在继续开始下一个remotecall_fetch()
操作之前等待每个操作完成,即等待每个进程完成其工作(在这种情况下为2秒)。在实际情况中,由于我们的流程无法同时完成其工作(即睡眠),因此我们在这里没有获得并行的好处。
但是,我们可以使用@async
和@sync
宏的组合来纠正此问题:
@time begin
a = cell(nworkers())
@sync for (idx, pid) in enumerate(workers())
@async a[idx] = remotecall_fetch(sleep, pid, 2)
end
end
## 2.009416 seconds (274 allocations: 25.592 KB)
现在,如果将循环的每个步骤都算作一个单独的操作,我们会看到在@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(sleep, pid, 2)
end
end
## 0.001429 seconds (27 allocations: 2.234 KB)
在这里,@async
宏允许我们甚至在每个remotecall_fetch()
操作完成执行之前就继续循环。但是,无论好坏,我们都没有@sync
宏来阻止代码继续经过此循环,直到所有remotecall_fetch()
操作完成为止。
尽管如此,remotecall_fetch()
即使我们继续进行,每个操作仍在并行运行。我们可以看到,因为如果我们等待两秒钟,那么包含结果的数组a将包含:
sleep(2)
julia> a
2-element Array{Any,1}:
nothing
nothing
(“ 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(sleep, pid, 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
而且,毫不奇怪,如果我们将@sync
和@async
紧挨着,我们会发现每个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(sleep, pid, 2)
end
end
# 4.019500 seconds (4.20 k allocations: 216.964 KB)
# Task (done) @0x0000000115e52a10
还要注意,在@async
宏范围内可能有更复杂的操作。该文档提供了一个示例,其中包含范围为的整个循环@async
。
更新:回想一下,对于同步宏国的帮助下,它会“等到所有动态封闭使用@async
,@spawn
,@spawnat
和@parallel
。完成” 就算是“完成”而言,如何在@sync
和@async
宏范围内定义任务很重要。考虑下面的示例,它与上面给出的示例之一略有不同:
@time begin
a = cell(nworkers())
@sync for (idx, pid) in enumerate(workers())
@async a[idx] = remotecall(sleep, pid, 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)
前面的示例大约花费了2秒钟的时间来执行,这表明这两个任务是并行运行的,并且脚本在继续执行之前等待每个任务完成其功能。但是,此示例的评估时间要短得多。原因是,就其目的而言,一旦将工人发送给工作,@sync
该remotecall()
操作就“完成”了。(请注意,结果数组a在这里仅包含RemoteRef对象类型,这仅表明特定进程正在发生某些事情,理论上可以在将来的某个时间获取)。相比之下,该remotecall_fetch()
操作仅在从工作人员处收到其任务已完成的消息时才“完成”。
因此,如果您正在寻找方法来确保对工作人员的某些操作在脚本中继续进行之前已经完成(例如本博文中所讨论的:等待在Julia中的远程处理器上完成任务),则有必要仔细考虑什么才算是“完整”,以及如何在脚本中对其进行衡量和实施。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句