Какой многопоточный пакет для Lua "просто работает", когда поставляется? - PullRequest
34 голосов
/ 17 апреля 2011

При кодировании на Lua у меня есть тройной вложенный цикл, который проходит 6000 итераций. Все 6000 итераций независимы и могут быть легко распараллелены. Какой пакет потоков для Lua компилируется из коробки , а получает приличное параллельное ускорение на четырех или более ядрах?

Вот что я знаю до сих пор:

  • luaproc исходит от основной команды Lua, но комплект программного обеспечения на luaforge устарел, и в списке рассылки есть сообщения об этом segfaulting. Кроме того, мне не очевидно, как использовать скалярную модель передачи сообщений для окончательного получения результатов в родительском потоке.

  • Луа Лейнс выдвигает интересные утверждения, но, кажется, является тяжелым, сложным решением. Многие сообщения в списке рассылки сообщают о проблемах, которые заставляют Lua Lanes создавать или работать на них. У меня самого были проблемы с тем, чтобы заставить работать основной механизм распределения "Lua пород".

  • LuaThread требует явной блокировки и требует, чтобы обмен данными между потоками осуществлялся глобальными переменными, которые защищены блокировками. Я мог представить себе хуже, но я был бы счастлив с более высоким уровнем абстракции.

  • Concurrent Lua предоставляет привлекательную модель передачи сообщений, аналогичную Erlang, но в ней говорится, что процессы не разделяют память. Не ясно, действительно ли spawn работает с любой функцией Lua или есть ограничения.

  • Рассел Кокс предложил модель случайных потоков , которая работает только для потоков 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 передается в качестве аргумента, поэтому пакет может быть полезен мне, только если он может выполнять произвольные функции параллельно. Моя цель - добиться достаточного параллелизма, чтобы обеспечить 100% загрузку ЦП на 6-8 ядрах.

Ответы [ 6 ]

2 голосов
/ 23 июля 2016

Проверьте библиотеку threads в семействе горелок. Он реализует модель пула потоков: сначала создаются несколько истинных потоков (pthread в linux и windows thread в win32). Каждый поток имеет объект lua_State и блокирующую очередь заданий, которая допускает добавление заданий из основного потока.

Объекты Lua копируются из основного потока в поток задания. Однако объекты C, такие как тензоры факелов или tds , могут передавать структуры данных в потоки заданий с помощью указателей - так достигается ограниченная общая память.

2 голосов
/ 07 сентября 2012

Норман написал по поводу luaproc:

"Для меня не очевидно, как использовать скалярную модель передачи сообщений для окончательного получения результатов в родительском потоке"

У меня была та же проблема с вариантом использования, с которым я имел дело. Мне понравился lua proc из-за его простой и легкой реализации, но в моем сценарии использования был C-код, который вызывал lua, вызывая сопрограмму, которая требовала отправки / получения сообщений для взаимодействия с другими потоками luaproc.

Для достижения желаемой функциональности мне пришлось добавить функции в luaproc, чтобы разрешать отправку и получение сообщений из родительского потока или любого другого потока, не запущенного из планировщика luaproc. Кроме того, мои изменения позволяют использовать luaproc для отправки / получения из сопрограмм, созданных из luaproc.newproc () созданных состояний lua.

Я добавил дополнительную функцию luaproc.addproc () в api, которая должна вызываться из любого состояния lua, запущенного из контекста, не контролируемого планировщиком luaproc, чтобы настроить себя с luaproc для отправки / получения сообщений.

Я планирую опубликовать исходный код как новый проект github или связаться с разработчиками и посмотреть, не захотят ли они добавить мои дополнения. Предложения о том, как я должен сделать это доступным для других, приветствуются.

1 голос
/ 21 марта 2017

Я сейчас создал параллельное приложение, используя luaproc.Вот некоторые заблуждения, которые помешали мне принять его раньше, и как обойти их.

  • После запуска параллельных потоков, насколько я могу судить нетспособ общения с родителем. Это свойство было для меня большим блоком.В конце концов я понял путь вперед: когда он завершает разветвление потоков, родитель останавливается и ждет.Задание, которое было бы выполнено родителем, вместо этого должно быть выполнено дочерним потоком, который должен быть выделен для этого задания.Не очень хорошая модель, но она работает.

  • Общение между родителями и детьми очень ограничено .Родитель может передавать только скалярные значения: строки, логические значения и числа.Если родитель хочет передать более сложные значения, такие как таблицы и функции, он должен кодировать их как строки.Такое кодирование может быть встроено в программу или (особенно) функции могут быть припаркованы в файловой системе и загружены в дочерний элемент с помощью require.

  • Дети не наследуют ничегосреды родителей. В частности, они не наследуют package.path или package.cpath.Мне пришлось обойти это, написав код для детей.

  • Самый удобный способ общения от родителя к ребенку - это определить ребенка как функцию ипусть ребенок захватит родительскую информацию в своих свободных переменных, известных на языке Lua как "upvalues".Эти свободные переменные не могут быть глобальными переменными, и они должны быть скалярами.Тем не менее, это достойная модель.Вот пример:

    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 передаются от родителя к потомку.

    Здесь важно утверждение, так как если вы забудете правила и случайно захватите таблицу или локальную функцию, luaproc.newproc потерпит неудачу.

Как только я пойму эти свойстваluaproc действительно работал «из коробки», когда загружено из askyrme на github .

ETA: раздражающее ограничение : в некоторых обстоятельствахвызов fread() в одном потоке может помешать планированию других потоков.В частности, если я запускаю последовательность

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

, операция read блокирует все остальные потоки .Я не знаю, почему это так - я предполагаю, что в glibc происходит какая-то ерундаОбходной путь, который я использовал, заключался в непосредственном вызове read(2), что требовало небольшого кода клея, но это правильно работает с io.popen и file:close().

. Есть еще одно ограничение, на которое стоит обратить внимание:1062 *

В отличие от оригинальной концепции передачи последовательной обработки Тони Хоара и в отличие от большинства зрелых серьезных реализаций синхронной передачи сообщений, luaproc не позволяет приемнику блокировать несколько каналов одновременно.Это серьезное ограничение, и оно исключает многие из шаблонов проектирования, в которых хорошо работает синхронная передача сообщений, но оно все еще можно найти для многих простых моделей параллелизма, особенно для типа "parbegin", который мне нужно было решить для моей исходной задачи.
1 голос
/ 23 мая 2011

Параллельная Lua может показаться подходящим вариантом, но, как я отмечаю в моих обновлениях ниже, она не работает параллельно. Подход, который я пробовал, был порождать несколько процессов, выполняющих засоленные замыканияполучил через очередь сообщений.

Обновление

Параллельно Lua, кажется, обрабатывает первоклассные функции и замыкания без заминки.См. Следующий пример программы.

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

Поцарапайте все вышеперечисленное.Что-то не выглядело правильно, когда я проверял это.Оказывается, Concurent Lua совсем не работает одновременно.«Процессы» реализованы с сопрограммами и все выполняются совместно в одном и том же контексте потока.Это то, что мы получаем за то, что не читаем внимательно!

Так что, по крайней мере, я исключил один из вариантов, я думаю.(

1 голос
/ 23 мая 2011

Это прекрасный пример MapReduce

Вы можете использовать LuaRings для выполнения ваших потребностей в распараллеливании.

1 голос
/ 17 апреля 2011

Я понимаю, что это не готовое к работе решение, но, может быть, перейдете на старую школу и поиграете с вилками?(Предполагая, что вы работаете в системе POSIX.)

Что бы я сделал:

  • Прямо перед циклом поместите все тесты в очередь, доступную между процессами.,(Файл, Redis LIST или что-нибудь еще, что вам нравится больше всего.)

  • Также перед циклом порождайте несколько вилок с lua-posix (столько же, сколько ядер или даже больше, в зависимостио характере испытаний).В родительском ответвлении дождитесь завершения всех дочерних процессов.

  • В каждом ответвлении цикла получите тест из очереди, выполните его, где-нибудь поместите результаты.(В файл, в список Redis LIST, где угодно.) Если в очереди больше нет тестов, выйдите из системы.

  • В родительской выборке обработайте все результаты теста по мере необходимости.do now.

Предполагается, что параметры и результаты теста можно сериализировать.Но даже если это не так, я думаю, что это должно быть довольно легко обмануть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...