什么multithreading包Lua“只是工作”出货?
在Lua编码,我有一个三重嵌套的循环,经历了6000次迭代。 所有的6000次迭代都是独立的,并且可以很容易地被并行化。 哪些线程包Lua 编译出来,并获得体面的并行加速四个或更多的核心?
以下是我所知道的:
-
luaproc
来自核心Lua团队,但luaforge上的软件包已经陈旧了,邮件列表中有报告。 另外,对于我来说,如何使用标量消息传递模型最终将结果导入到父线程中并不明显。 -
Lua Lanes提出有趣的说法,但似乎是一个重量级的,复杂的解决scheme。 邮件列表上的许多消息报告让Lua Lanes构build或为其工作带来麻烦。 我自己也难以得到潜在的“Lua岩石”分配机制为我工作。
-
LuaThread需要显式locking,并要求线程之间的通信由受锁保护的全局variables调解。 我可以想象得更糟,但我会更高兴地抽象。
-
并发Lua提供了一个类似于Erlang的有吸引力的消息传递模型,但它表示进程不共享内存。 目前还不清楚
spawn
实际上是否适用于任何 Lua函数或是否有限制。 -
Russ Cox提出了一个偶尔的只适用于C线程的线程模型。 对我没有用处。
我将upvote所有报告的实际经验与这些或任何其他multithreading包,或任何答案,提供新的信息。
作为参考,这里是我想并行化的循环:
for tid, tests in pairs(tests) do local results = { } matrix[tid] = results for i, test in pairs(tests) do if test.valid then results[i] = { } local results = results[i] for sid, bin in pairs(binaries) do local outcome, witness = run_test(test, bin) results[sid] = { outcome = outcome, witness = witness } end end end end
run_test
函数作为参数传入,所以只有在可以并行运行任意函数的情况下,程序包才会对我有用。 我的目标是有足够的并行性,在6到8个核心上获得100%的CPU利用率。
诺曼写到关于luaproc:
“对于我来说,如何使用标量消息传递模型最终获得结果到父线程中并不明显”
我遇到了与我正在处理的用例相同的问题。 我喜欢lua proc,因为它的实现简单明了,但是我的用例有一个叫做lua的C代码,它触发了一个需要发送/接收消息来与其他luaproc线程交互的协程序。
为了达到我想要的function,我不得不向luaproc添加function,以允许从父线程或任何其他线程发送和接收来自luaproc调度程序的消息。 此外,我的更改允许使用luaproc发送/接收从luaproc.newproc()创build的协程创buildlua状态。
我为api添加了一个额外的luaproc.addproc()函数,这个函数是从luaproc调度器没有控制的上下文中运行的,为了使用luaproc来发送/接收消息。
我正在考虑将源代码发布为新的github项目,或者联系开发人员,看看他们是否愿意提供我的补充。 对于如何让其他人可以使用的build议表示欢迎。
检查火炬族的线程库。 它实现了一个线程池模型:首先创build一些真正的线程(linux中的pthread和win32中的windows线程)。 每个线程都有一个lua_State对象和一个阻塞作业队列,用于接受从主线程添加的作业。
Lua对象从主线程复制到作业线程。 然而,像Torch张量或tds数据结构这样的C对象可以通过指针传递给作业线程 – 这就是共享内存的实现方式。
这是MapReduce的一个很好的例子
你可以使用LuaRings来完成你的并行化需求。
并发Lua似乎是要走的路,但正如我在下面的更新中注意到的, 它并不是并行运行。 我试过的方法是产生几个执行通过消息队列接收的pickle闭包的进程。
更新
并发Lua似乎处理一stream的function和closures顺利。 请参阅下面的示例程序。
require 'concurrent' local NUM_WORKERS = 4 -- number of worker threads to use local NUM_WORKITEMS = 100 -- number of work items for processing -- calls the received function in the local thread context function worker(pid) while true do -- request new work concurrent.send(pid, { pid = concurrent.self() }) local msg = concurrent.receive() -- exit when instructed if msg.exit then return end -- otherwise, run the provided function msg.work() end end -- creates workers, produces all the work and performs shutdown function tasker() local pid = concurrent.self() -- create the worker threads for i = 1, NUM_WORKERS do concurrent.spawn(worker, pid) end -- provide work to threads as requests are received for i = 1, NUM_WORKITEMS do local msg = concurrent.receive() -- send the work as a closure concurrent.send(msg.pid, { work = function() print(i) end, pid = pid }) end -- shutdown the threads as they complete for i = 1, NUM_WORKERS do local msg = concurrent.receive() concurrent.send(msg.pid, { exit = true }) end end -- create the task process local pid = concurrent.spawn(tasker) -- run the event loop until all threads terminate concurrent.loop()
更新2
以上所有的东西都划伤了。 当我testing这个时,有些东西看起来不正确。 事实certificate,并发Lua根本不是并发的。 “进程”是通过协程来实现的,并且都在同一个线程环境中协同运行。 这就是我们得不到仔细阅读!
所以,至less我消除了我猜想的一个选项。 🙁
我意识到,这不是一个开箱即用的解决scheme,但是,也许去老派和玩弄叉子? (假设你在POSIX系统上)
我会做什么:
-
在你的循环之前,把所有的testing放在一个队列中,可以在进程间访问。 (一个文件,一个Redis LIST或者其他你最喜欢的东西。)
-
同样在循环之前,用
lua-posix
产生几个分支(根据testing的性质,核心的数量甚至更多)。 在父母的分岔等待,直到所有的孩子将退出 -
在循环中的每个分支中,从队列中获得一个testing,执行它,把结果放在某个地方。 (到文件,到Redis LIST,你喜欢的任何地方。)如果在队列中没有更多的testing,退出。
-
在父提取和处理所有的testing结果,你现在做。
这假设testing参数和结果是可序列化的。 但即使他们不这样做,我认为这应该是相当容易的。
我现在使用luaproc
构build了一个并行应用程序。 以下是一些误解,使我无法尽早采纳,以及如何解决这些问题。
-
一旦并行线程启动,据我所知,他们没有办法与父母沟通。 这个属性是我的大块。 最终我意识到了前进的方向:当它完成分支线程时,父节点停止并等待。 父母应该完成的工作应该由一个应该专门用于该工作的子线程来完成。 不是一个伟大的模型,但它的工作原理。
-
父母与孩子之间的交stream非常有限 。 父母只能交stream标量值:string,布尔值和数字。 如果父类想要传递更复杂的值,比如表和函数,它必须将它们编码为string。 这样的编码可以在程序中内联地进行,或者(特别是)可以将function停放在文件系统中,并且可以使用
require
将其加载到小孩中。 -
孩子们没有inheritance父母的环境。 特别是,它们不会inheritance
package.path
或package.cpath
。 我必须按照我为孩子编写代码的方式来解决这个问题。 -
从父母到孩子沟通最方便的方式是将孩子定义为一个函数,并让孩子在其自由variables中获取父母信息,在Lua的说法中称为“upvalues”。 这些自由variables可能不是全局variables,它们必须是标量。 不过,这是一个体面的模型。 这是一个例子:
local function spawner(N, workers) return function() local luaproc = require 'luaproc' for i = 1, N do luaproc.send('source', i) end for i = 1, workers do luaproc.send('source', nil) end end end
此代码被用作,例如,
assert(luaproc.newproc(spawner(randoms, workers)))
这个电话就是价值
randoms
和workers
从父母到孩子的沟通方式。在这里断言是必不可less的,就像你忘记了规则并且意外地捕获了一个表或者一个本地函数一样,
luaproc.newproc
将会失败。
一旦我了解了这些属性, luaproc
确实可以“开箱即用”, 在github上从askyrme下载 。
ETA:有一个恼人的限制 :在某些情况下,在一个线程中调用fread()
可以阻止其他线程被调度。 特别是,如果我运行序列
local file = io.popen(command, 'r') local result = file:read '*a' file:close() return result
read
操作会阻塞所有其他线程 。 我不知道这是为什么 – 我认为这是一些胡言乱语的胡言乱语。 我使用的解决方法是直接调用read(2)
,这需要一个胶水代码,但这适用于io.popen
和file:close()
。
还有一个值得注意的限制:
- 与托尼·霍尔(Tony Hoare)原始的通信顺序处理概念不同,与大多数成熟,严格的同步消息传递实现不同,
luaproc
不允许接收者同时在多个通道上阻塞。 这种限制是严重的,它排除了许多同步消息传递所擅长的devise模式,但是仍然可以find许多简单的并行模型,特别是我需要解决的原始问题的“parbegin”sorting。