哪个Lua的多线程包“原装即用”?

在Lua中编码时,我有一个三重嵌套循环,需要进行6000次迭代。所有的6000次迭代都是独立的,可以很容易地并行化处理。哪个Lua线程包“编译开箱即用”并在四个或四个以上的内核上获得较好的并行速度提升?

到目前为止,我所知道的是:

  • luaproc 来自核心 Lua 团队,但是 luaforge 上的软件包很旧,邮件列表中有报道它会导致段错误。另外,对我来说如何使用标量消息传递模型来将结果最终传递到父线程不太明显。

  • Lua Lanes 提出了有趣的主张,但似乎是一种笨重、复杂的解决方案。邮件列表中有许多消息报告了在让 Lua 车道为他们建立或工作时出现的困难。我自己也有困难让底层的“Lua 石头”分发机制为我工作。

  • LuaThread 需要显式加锁,并要求线程之间的通信通过由锁保护的全局变量来执行。我可以想象更糟糕的情况,但我会更喜欢更高级的抽象。

  • Concurrent Lua 提供了一个类似 Erlang 的有吸引力的消息传递模型,但是它说进程不共享内存。目前不清楚 'spawn' 实际上是否与 任何 Lua 函数一起工作,或者是否有限制。

  • Russ Cox 提出了一个偶发线程模型,只能处理 C 线程。对我没用。

对于这些或任何其他多线程包的实际经验的回答,我将对所有回答进行投票,或提供新的信息。


供参考,这里是我想并行化的循环:

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 利用率。

原文链接 https://stackoverflow.com/questions/5689548

点赞
stackoverflow用户6236
stackoverflow用户6236

我意识到这不是一个开箱即用的解决方案,但是,可以尝试老派的fork操作吗?(假定你在POSIX系统上。)

我会这样做:

  • 在你的循环之前,把所有的测试放在一个队列里,可以在进程之间访问。(可以是一个文件、一个Redis LIST或任何你最喜欢的方式。)

  • 在循环之前,使用 lua-posix 来生成几个fork(与测试的性质有关的内核数,甚至更多)。在父级fork中等待所有子级退出。

  • 在每个fork的循环中,从队列中获取一个测试,执行它,将结果放在某个地方。(到一个文件,到一个Redis LIST,到任何你喜欢的地方。)如果队列中没有更多的测试,就退出。

  • 在父进程中,提取和处理所有测试结果,就像你现在所做的。

这假定测试参数和结果是串行化的。但是即使它们不能,我认为绕过这个问题也应该相当容易。

2011-04-16 23:32:12
stackoverflow用户401839
stackoverflow用户401839

这是一个完美的 MapReduce 示例

你可以使用 LuaRings 来实现你的并行化需求。

2011-05-23 03:09:47
stackoverflow用户1491
stackoverflow用户1491

Concurrent Lua 似乎是可行的选择,但正如我在下面的更新中所指出的那样,它并不能并发地运行程序。我尝试的方法是通过消息队列接收到一些以 pickle 形式储存的闭包并生成多个进程来执行它们。

更新

Concurrent Lua 似乎可以处理一等函数和闭包而没有任何问题。请看下面的示例程序。

require 'concurrent'

local NUM_WORKERS = 4       -- 使用的工作线程数
local NUM_WORKITEMS = 100   -- 要处理的工作项目数

-- 在本地线程上下文中调用接收的函数
function worker(pid)
    while true do
        -- 请求新的工作
        concurrent.send(pid, { pid = concurrent.self() })
        local msg = concurrent.receive()

        -- 在指令终止时退出
        if msg.exit then return end

        -- 否则,运行提供的函数
        msg.work()
    end
end

-- 创建工作线程,产生所有的工作并进行关闭
function tasker()
    local pid = concurrent.self()

    -- 创建工作线程
    for i = 1, NUM_WORKERS do concurrent.spawn(worker, pid) end

    -- 当请求被接收时为线程提供工作
    for i = 1, NUM_WORKITEMS do
        local msg = concurrent.receive()

        -- 将工作发送为闭包
        concurrent.send(msg.pid, { work = function() print(i) end, pid = pid })
    end

    -- 在线程完成后进行关闭
    for i = 1, NUM_WORKERS do
        local msg = concurrent.receive()
        concurrent.send(msg.pid, { exit = true })
    end
end

-- 创建任务进程
local pid = concurrent.spawn(tasker)

-- 循环运行事件,直到所有线程终止
concurrent.loop()

更新 2

刚刚那些都别管了。当我测试时,某些事情似乎不对劲。结果发现 Concurrent Lua 根本不是并发的。它的“进程”是用协程实现的,而且全部在同一线程上下文中协同运行。这就是我们因为没仔细阅读而犯下的错误!

所以,至少我消除了其中的一些选项,我猜这就是了。 :(

2011-05-23 03:44:27
stackoverflow用户1655068
stackoverflow用户1655068

Norman 在谈到 luaproc 时写道:

"it's not obvious to me how to use the scalar message-passing model to get results ultimately into a parent thread"

我也遇到了同样的问题。我喜欢 luaproc,因为它的实现简单而轻便,但我的使用场景涉及到调用 lua 的 C 代码,该代码会触发协程来发送/接收消息以与其他 luaproc 线程互动。

为了实现我想要的功能,我不得不向 luaproc 添加功能,以允许从父线程或任何不受 luaproc 调度程序控制的线程发送和接收消息。此外,我的更改允许从 luaproc.newproc() 创建的协程中使用 luaproc send/receive。

我在 API 中添加了一个额外的 luaproc.addproc() 函数,该函数应该从任何不受 luaproc 调度程序控制的上下文中运行的 lua 状态中调用,以便为发送/接收消息建立 luaproc。

我考虑将源代码作为新的 GitHub 项目或联系开发人员,看看他们是否希望拉取我的添加内容。欢迎提出如何向其他人提供此代码的建议。

2012-09-07 15:04:56
stackoverflow用户183828
stackoverflow用户183828

在Torch家族中,查看threads库。它实现了线程池模型:首先创建几个真正的线程(Linux中的pthread和Windows中的win32线程)。每个线程都有一个lua_State对象和一个阻塞的作业队列,可以从主线程中添加任务。

Lua对象被复制到作业线程中。然而C对象,比如Torch tensorstds数据结构,可以通过指针传递到作业线程--这就是如何实现有限共享内存的。

2016-07-23 13:48:48
stackoverflow用户41661
stackoverflow用户41661

我现在使用luaproc构建了一个并行应用程序。以下是一些曾经阻止我采用它的错误观念,以及如何解决它们的方法。

  • 一旦启动了并行线程,据我所知,它们无法回传信息给父线程。这一属性是我面临的一大障碍。最终我找到了前进的道路:当它完成线程分支后,父线程停止并等待。父线程将要完成的工作应由子线程完成,该子线程应专门致力于该工作。虽然不是一个很好的模型,但这样做确实可行。

  • 父线程和子线程之间的通信非常有限。父线程只能通信到标量值:字符串、布尔值和数字。如果父线程希望通信更复杂的值,如表格和函数,则必须将它们编码为字符串。这样的编码可以在程序中进行内联,或者(特别是)函数可以被停放到文件系统中,并使用require将其加载到子线程中。

  • 子线程不继承父线程的任何环境。特别是它们不会继承package.pathpackage.cpath。我通过编写子程序的方式来解决这个问题。

  • 父线程向子线程通信的最便捷方法是将子线程定义为一个函数,并使子线程在其自由变量中捕获父线程的信息,称为Lua术语中的“上值”。这些自由变量不得是全局变量,它们必须是标量。尽管如此,这仍然是一个不错的模型。以下是一个例子:

    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)))
    

    这个调用是如何从父线程传递值randomsworkers到子线程的。

    这里的断言是必要的,因为如果忘记规则并意外地捕捉表或本地函数,luaproc.newproc将失败。

一旦我理解了这些特性,从github上的askyrme下载luaproc确实可以“开箱即用”。

估计时间:存在一个恼人的限制:在某些情况下,调用一个线程中的fread()可能会阻止其他线程被调度。特别是,如果我运行以下的序列:

local file = io.popen(command, 'r')
local result = file:read '*a'
file:close()
return result

read操作会阻塞所有其他线程。我不知道这是为什么 - 我认为这是在glibc内部进行的一些胡言乱语。我使用的解决方法是直接调用read(2),这需要一些粘合代码,但它可以正确地与io.popenfile:close()一起工作。

还有一个值得注意的限制:

  • 与Tony Hoare的原始通信顺序处理的概念以及大多数成熟的、严肃的同步消息传递实现不同,luaproc不允许接收器同时在多个通道上阻塞。这种限制是严重的,并排除了许多同步消息传递擅长的设计模式,但对于许多简单的并行模型仍然有效,特别是我需要解决的“parbegin”类型的并行。
2017-03-21 14:28:50