English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

协同程序 Lua (coroutine)

Что такое координаты (coroutine)?

Lua координаты (coroutine) очень похожи на потоки: имеют независимый стек, независимые локальные переменные, независимый указатель команды, и в то же время совместно используют глобальные переменные и многое другое с другими координатами.

Координаты - это очень мощная функция, но также и сложная в использовании.

Различие между потоками и координатами

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

В каждый момент времени выполняется только одна координата, и координата, выполняющаяся, приостанавливается только по требованию.

Координаты несколько напоминают синхронизированные многопоточность, несколько потоков, ожидающих одного и того же mutex, напоминают координаты.

Основная грамматика

МетодОписание
coroutine.create()Создание coroutine, возвращает coroutine, параметром является функция, при использовании совместно с resume функция вызывается
coroutine.resume()Перезапуск coroutine, совместно с create
coroutine.yield()Приостановка coroutine, устанавливает coroutine в состояние приостановки, что, совместно с resume, может дать много полезных эффектов
coroutine.status()Просмотр состояния coroutine
Примечание: состояние coroutine может быть одним из трех: dead, suspended, running. Когда именно наступает такое состояние, см.下面的程序
coroutine.wrap()Создание coroutine, возвращает функцию, вызов которой приводит к входу в coroutine, что дублирует функцию create
coroutine.running()возвращает运行的 coroutine, coroutine - это поток, при использовании running возвращает номер потока coroutine

ниже приведен пример использования вышеуказанных методов:

-- coroutine_test.lua файл
co = coroutine.create(
    function(i)
        print(i);
    end
)
 
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
 
print("----------")
 
co = coroutine.wrap(
    function(i)
        print(i);
    end
)
 
co(1)
 
print("----------")
 
co2 = coroutine.create(
    function()
        for i = 1, 10 do
            print(i)
            if i == 3 then
                print(coroutine.status(co2)) -- running
                print(coroutine.running()) -- thread:XXXXXX
            end
            coroutine.yield()
        end
    end
)
 
coroutine.resume(co2) -- 1
coroutine.resume(co2) -- 2
coroutine.resume(co2) -- 3
 
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
 
print("----------")

Результат выполнения примера выше:

1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868 false
suspended
thread: 0x7fb801c04c88 true
----------

function running можно увидеть, что coroutine в底层 реализована как поток

при создании coroutine в новом потоке регистрируется событие

при использовании resume для инициирования события функция coroutine.create выполняется, при встрече с yield текущий поток приостанавливается, ожидая повторного инициирования события resume

далее мы анализируем более детальный пример:

function foo(a)
    print("foo функция вывод", a)
    return coroutine.yield(2 * a) -- возвращает значение 2*a
end
 
co = coroutine.create(function (a , b)}
    print("Первый раз выполнение协同 program выводит", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    print("Второй раз выполнение协同 program выводит", r)
    local r, s = coroutine.yield(a + b, a - b) -- а, b являются значениями,传入 при первом вызове协同 program
     
    print("Третий раз выполнение协同 program выводит", r, s)
    return b, "окончить协同 program"                   -- значение b является значением,传入 при втором вызове协同 program
end)
        
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--Разделительная линия----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---Разделительная линия---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---Разделительная линия---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---Разделительная линия---")

Результат выполнения примера выше:

Первый раз выполнение协同 program выводит    1    10
вывод функции foo    2
main    true    4
--Разделительная линия----
Второй раз выполнение协同 program выводит    r
main    true    11    -9
---Разделительная линия---
Третий раз выполнение协同 program выводит    x    y
main    true    10    окончить协同 program
---Разделительная линия---
main    false    cannot resume dead coroutine
---Разделительная линия---

Пример продолжается следующим образом:

  • Вызов resume, чтобы唤醒协同程序, операция resume успешна и возвращает true,否则 возвращает false;

  • 协同程序运行;

  • Доходят до строки yield;

  • yield приостанавливает协同程序, первый resume возвращает;( 注意:此处 yield 返回, параметр является параметром resume)

  • Второй раз唤醒协同程序;( 注意:此处 resume 参数中,除了第一个参数,其余参数将作为 yield 参数)

  • return;

  • Синхронизация продолжается;

  • Если используемый协同program продолжает работать и вызывается resume метод после завершения, выводится: cannot resume dead coroutine

Сила сочетания resume и yield заключается в том, что resume находится в основном коде, он传入 внешние состояния (данные) в协同program; а yield возвращает внутренние состояния (данные) в основной код.

Проблема производителя-потребителя

Теперь я использую协同program Lua для решения классической проблемы производителя-потребителя.

local newProductor
function productor()
     local i = 0
     while true do
          i = i + 1
          send(i) -- Отправка производимого товара потребителю
     end
end
function consumer()
     while true do
          local i = receive() -- Получение товара от производителя
          print(i)
     end
end
function receive()
     local status, value = coroutine.resume(newProductor)
     return value
end
function send(x)
     coroutine.yield(x) -- x указывает на значение, которое нужно отправить. После отправки значения,协同程序 приостанавливается
end
-- Запуск программы
newProductor = coroutine.create(productor)
consumer()

Результат выполнения примера выше:

1
2
3
4
5
6
7
8
9
10
11
12
13
……