Шаблон «производитель—потребитель»
Реализуйте конвейер «производитель—потребитель» с помощью сопрограмм.
«Шаблон «производитель—потребитель»» — бесплатный урок Lua Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Lua Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Lua Academy содержит 4 уроков всего.
Объяснение шаблона
Шаблон «производитель — потребитель» с корутинами: производитель создаёт данные и выдаёт каждый элемент через yield, а потребитель получает элементы по одному. Ни одному из них не нужно знать устройство другого — они обмениваются данными через yield/resume. Это отделяет создание данных от их обработки.
local function makeProducer(items)
return coroutine.create(function()
for _, item in ipairs(items) do
coroutine.yield(item)
end
end)
end
local function consume(producer)
while true do
local ok, item = coroutine.resume(producer)
if not ok or item == nil then break end
print("Processing:", item)
end
end
consume(makeProducer({10, 20, 30, 40}))Бесконечный производитель
Корутина-производитель может выдавать бесконечную последовательность. Потребитель управляет моментом остановки. Это идеально подходит для потоковых источников данных: показаний датчиков, событий журнала и генерируемых последовательностей.
local function primes()
return coroutine.wrap(function()
local function isPrime(n)
if n<2 then return false end
for i=2, math.floor(math.sqrt(n)) do
if n%i==0 then return false end
end
return true
end
local n = 2
while true do
if isPrime(n) then coroutine.yield(n) end
n = n + 1
end
end)
end
local gen = primes()
for i = 1, 10 do
io.write(gen() .. " ")
end
print() -- 2 3 5 7 11 13 17 19 23 29Потребитель как корутина
Потребитель тоже может быть корутиной, образуя полностью симметричную пару производителя и потребителя. Третья функция — «диспетчер» — координирует их работу. Это полезно для буферизованных конвейеров, в которых производитель и потребитель работают с разной скоростью.
local function producer()
return coroutine.create(function(consumer)
for i = 1, 5 do
coroutine.resume(consumer, i * 10)
coroutine.yield() -- wait
end
end)
end
local consumer = coroutine.create(function()
while true do
local val = coroutine.yield()
print("Received:", val)
end
end)
coroutine.resume(consumer) -- start, waits for data
local pro = producer()
for i = 1, 5 do
coroutine.resume(pro, consumer)
endОбратное давление
Обратное давление возникает, когда потребитель сообщает производителю, что нужно снизить скорость. С корутинами это происходит естественно: производитель выполняет yield после каждого элемента и ждёт, пока потребитель запросит следующий. Если потребитель работает медленно, производитель просто ждёт.
local function slowConsumer(gen)
for v in gen do
-- Simulate slow processing
print("Processing", v, "...")
-- In real code: os.execute("sleep 0.1")
end
end
local function fastProducer(n)
return coroutine.wrap(function()
for i = 1, n do
print("Producing", i)
coroutine.yield(i)
end
end)
end
-- Producer automatically waits for consumer:
slowConsumer(fastProducer(4))Пакетная обработка
Производитель, работающий пакетами, собирает N элементов, прежде чем выдать пакет. Потребитель получает и обрабатывает целые пакеты. Это уменьшает накладные расходы на координацию у производителей с высокой частотой выдачи данных.
local function batchProducer(items, batchSize)
return coroutine.wrap(function()
local batch = {}
for _, item in ipairs(items) do
batch[#batch+1] = item
if #batch >= batchSize then
coroutine.yield(batch)
batch = {}
end
end
if #batch > 0 then coroutine.yield(batch) end
end)
end
local data = {1,2,3,4,5,6,7,8,9,10}
for batch in batchProducer(data, 3) do
print("Batch:", table.concat(batch, ","))
end
-- Batch: 1,2,3
-- Batch: 4,5,6
-- Batch: 7,8,9
-- Batch: 10Производитель строк файла
Читайте файл лениво: корутина-производитель считывает строки по одной и выдаёт каждую через yield. Потребитель обрабатывает строки, не загружая весь файл в память.
local function lineProducer(path)
return coroutine.wrap(function()
local f = io.open(path, "r")
if not f then return end
for line in f:lines() do
coroutine.yield(line)
end
f:close()
end)
end
-- Process file line by line without loading all at once
local wordCount = 0
for line in lineProducer("data.txt") do
for _ in line:gmatch("%S+") do
wordCount = wordCount + 1
end
end
print("Words:", wordCount)Этап преобразования
Добавьте этап преобразования между производителем и потребителем. Преобразователь считывает данные из одной корутины и передаёт обработанные значения другой. Создавайте конвейеры данных, соединяя такие этапы.
local function transform(source, fn)
return coroutine.wrap(function()
for v in source do
coroutine.yield(fn(v))
end
end)
end
local numbers = coroutine.wrap(function()
for i = 1, 6 do coroutine.yield(i) end
end)
local doubled = transform(numbers, function(n) return n*2 end)
local filtered = coroutine.wrap(function()
for v in doubled do
if v > 4 then coroutine.yield(v) end
end
end)
for v in filtered do io.write(v.." ") end
print() -- 6 8 10 12Передача ошибок в конвейерах
Ошибки в конвейерах корутин передаются через состояние завершения или с помощью pcall. Оборачивайте этапы конвейера в pcall, чтобы перехватывать ошибки и корректно обрабатывать их, не нарушая работу всего конвейера.
local function safeStage(source, fn)
return coroutine.wrap(function()
for v in source do
local ok, result = pcall(fn, v)
if ok then
coroutine.yield(result)
else
print("Error processing", v, ":", result)
end
end
end)
end
local data = coroutine.wrap(function()
for _, v in ipairs({4, -1, 9, 0, 16}) do coroutine.yield(v) end
end)
local results = safeStage(data, function(n)
assert(n > 0, "non-positive")
return math.sqrt(n)
end)
for v in results do print(v) endОбъединение производителей
Объедините нескольких производителей в одного: по очереди обращайтесь ко всем активным производителям и выдавайте по одному элементу от каждого. Остановитесь, когда все производители исчерпают данные.
local function merge(...)
local producers = {...}
return coroutine.wrap(function()
while #producers > 0 do
local alive = {}
for _, co in ipairs(producers) do
local ok, v = coroutine.resume(co)
if ok and v ~= nil then
coroutine.yield(v)
alive[#alive+1] = co
end
end
producers = alive
end
end)
end
local function src(items)
return coroutine.create(function()
for _,v in ipairs(items) do coroutine.yield(v) end
end)
end
for v in merge(src({1,3,5}),src({2,4,6})) do
io.write(v.." ")
end
print() -- 1 2 3 4 5 6Практический пример
Конвейер обработки журналов: производитель считывает строки журнала, этап фильтрации оставляет только строки с ошибками, этап преобразования извлекает ключевую информацию, а потребитель группирует данные по типу ошибки. Каждый этап — независимая корутина, которую можно тестировать отдельно.
local function makeFilter(src, pred)
return coroutine.wrap(function()
for line in src do
if pred(line) then coroutine.yield(line) end
end
end)
end
local logs = coroutine.wrap(function()
local entries = {
"INFO: started",
"ERROR: timeout",
"DEBUG: connecting",
"ERROR: auth failed",
}
for _, e in ipairs(entries) do coroutine.yield(e) end
end)
local errors = makeFilter(logs, function(l) return l:match("^ERROR") end)
for line in errors do print(line) end
-- ERROR: timeout
-- ERROR: auth failedБыстрая проверка
Как шаблон «производитель — потребитель» на основе корутин обрабатывает обратное давление?
Итоги: производитель и потребитель
Краткое содержание:
- Производитель выдаёт элементы; потребитель управляет им, вызывая resume
- Бесконечные производители:
while true do yield() end - Конвейеры: соединяйте корутины преобразования между производителем и потребителем
- Обратное давление возникает автоматически: производитель ждёт потребителя
- Используйте обёртки-корутины для этапов пакетной обработки, фильтрации и объединения
- Применяйте pcall на этапах конвейера для изоляции ошибок
Часто задаваемые вопросы
Урок «Шаблон «производитель—потребитель»» бесплатный?
Да — полный текст урока «Шаблон «производитель—потребитель»» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Lua Academy, подпишись на CoddyKit PRO. Курс Lua Academy содержит 4 уроков всего.
Чему я научусь в уроке «Шаблон «производитель—потребитель»»?
Реализуйте конвейер «производитель—потребитель» с помощью сопрограмм. Ты практикуешь Lua Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Lua Academy?
Предыдущий опыт не требуется. Lua Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Шаблон «производитель—потребитель»»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Lua Academy?
Да. Каждый урок Lua Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Создание сопрограмм
- resume и yield
- Состояние сопрограммы
- Шаблон «производитель—потребитель»