Coroutine

Konkurensi kooperatif dengan coroutine beserta pola produsen dan konsumen.

Daftar isi

Sebuah coroutine adalah fungsi yang pelaksanaannya dapat ditunda dan dilanjutkan, sehingga program dapat berhenti sejenak di tengah proses lalu kembali ke titik yang sama dengan keadaan yang tetap utuh. Lua mendukung coroutine secara penuh melalui pustaka coroutine. Topik ini membahas fungsi coroutine.create, coroutine.resume, coroutine.yield, coroutine.status, dan coroutine.wrap, kemudian menerapkannya pada pola produsen-konsumen (producer-consumer) serta pipeline, yaitu rangkaian tahapan pemrosesan data. Setelah membacanya, deret data yang dihasilkan sedikit demi sedikit serta aliran data bertahapan dapat ditulis secara berurutan dan mudah dibaca.

Coroutine sebagai pelaksana kooperatif#

Fungsi coroutine.create menerima sebuah fungsi dan mengembalikan objek coroutine yang pelaksanaannya belum dimulai. Pelaksanaan baru berjalan ketika coroutine.resume memanggil coroutine tersebut. Di dalam tubuh coroutine, coroutine.yield menunda pelaksanaan dan mengirim nilai kepada pemanggil resume, sedangkan nilai yang dikirim resume berikutnya diterima oleh yield sebagai nilai balik. Fungsi coroutine.status melaporkan keadaan coroutine pada saat itu.

Contoh berikut menelusuri satu siklus tunda dan lanjut secara lengkap. Simpan berkasnya, jalankan dengan lua coroutine-dasar.lua, lalu bandingkan setiap baris keluaran dengan baris kodenya.

lua coroutine-dasar.lua
-- coroutine-dasar.lua
-- create, resume, yield, dan status pada satu coroutine.

local ko = coroutine.create(function(a, b)
  print("status di dalam", coroutine.status(coroutine.running()))
  print("mulai dengan jumlah", a + b)
  local dikirim = coroutine.yield(a * 2)
  print("lanjut dengan", dikirim)
  return "selesai"
end)

print(coroutine.status(ko))
print(coroutine.resume(ko, 10, 20))
print(coroutine.status(ko))
print(coroutine.resume(ko, "balikan"))
print(coroutine.status(ko))
text Keluaran
suspended
status di dalam	running
mulai dengan jumlah	30
true	20
suspended
lanjut dengan	balikan
true	selesai
dead

Baris pertama menunjukkan coroutine yang baru dibuat masih suspended. Pemanggilan resume pertama mengirim 10 dan 20 sebagai argumen fungsi, menjalankan tubuh hingga yield, lalu mengembalikan true dan hasil yield, yaitu 20. Pemanggilan resume kedua mengirim balikan, yang diterima variabel dikirim, dan tubuh coroutine selesai dengan return, sehingga resume mengembalikan true beserta nilai balik fungsi. Setelah itu statusnya menjadi dead dan coroutine tidak dapat dilanjutkan lagi.

Nilai statusMakna
suspendedCoroutine dibuat atau sedang ditunda pada yield.
runningCoroutine sedang mengeksekusi tubuhnya.
normalCoroutine sedang aktif tetapi sedang melanjutkan coroutine lain.
deadCoroutine telah menyelesaikan tubuhnya atau berhenti karena error.

Lua mengeksekusi coroutine secara kooperatif dan berurutan. Pada satu saat, hanya satu coroutine yang berjalan, dan peralihan kendali terjadi hanya melalui coroutine.yield atau akhir tubuh coroutine. Tidak ada penjadwal sistem operasi yang menyela pelaksanaan di tengah jalan, sehingga urutan akses data sepenuhnya ditentukan oleh pemanggilan resume yang tertulis di dalam kode.

Pola produsen dan konsumen#

Pola produsen dan konsumen memisahkan pembuatan data dari penggunaan data. Produsen menghasilkan deret data satu per satu, sedangkan konsumen mengambil keping sebanyak yang dibutuhkan. Coroutine membuat pola ini rapi karena produsen dapat ditulis sebagai perulangan biasa yang memanggil coroutine.yield, tanpa menyimpan indeks atau keadaan lain secara manual.

Fungsi coroutine.wrap memangkas kerangka penulisan pola ini. Fungsi tersebut membungkus sebuah fungsi menjadi coroutine baru, lalu mengembalikan fungsi biasa yang setiap pemanggilannya melakukan resume. Nilai hasil yield diterima langsung sebagai nilai balik pemanggilan tersebut.

lua produsen-genap.lua
-- produsen-genap.lua
-- Produsen menghasilkan deret genap, konsumen mengambil sejumlah keping.

local function produsenGenap(batas)
  return coroutine.wrap(function()
    for nilai = 2, batas, 2 do
      coroutine.yield(nilai)
    end
  end)
end

local ambilKeping = produsenGenap(30)

for _ = 1, 5 do
  print(ambilKeping())
end
text Keluaran
2
4
6
8
10

Konsumen pada contoh ini mengambil lima keping pertama dari produsen yang mampu menghasilkan lima belas bilangan. Produsen tetap ditulis sebagai perulangan sederhana, sedangkan keadaan perulangannya tersimpan di dalam coroutine antarkeping. Apabila keping keenam diminta, produsen menghasilkan 12, dan seterusnya hingga batas 30 tercapai.

Pipeline dengan beberapa coroutine#

Coroutine dapat disambung menjadi pipeline, yaitu rangkaian tahapan pemrosesan data. Setiap tahap menarik data dari tahap sebelumnya, mengolahnya, lalu meneruskannya kepada tahap berikutnya. Data mengalir melalui coroutine.yield, sedangkan permintaan data mengalir balik melalui pemanggilan fungsi hasil coroutine.wrap.

Contoh berikut memproses catatan log sensor suhu dalam dua tahap. Fungsi produsenBaris menghasilkan baris data satu per satu. Fungsi penyaringSuhu membaca dari sumber, mengurai nama sensor dan suhu dengan baris:match, lalu meneruskan hanya baris dengan suhu di atas ambang 30. Perulangan terakhir bertindak sebagai konsumen yang mencetak baris yang lolos.

lua pipeline.lua
-- pipeline.lua
-- Produsen menghasilkan baris data, penyaring memilih, konsumen mencetak.

local function produsenBaris(data)
  return coroutine.wrap(function()
    for _, baris in ipairs(data) do
      coroutine.yield(baris)
    end
  end)
end

local function penyaringSuhu(sumber, ambang)
  return coroutine.wrap(function()
    for baris in sumber do
      local nama, suhu = baris:match("^(%S+) (-?%d+)$")
      if suhu and tonumber(suhu) > ambang then
        coroutine.yield(baris)
      end
    end
  end)
end

local dataSuhu = {"sensorA 31", "sensorB 27", "sensorA 33", "sensorC 25"}
local saring = penyaringSuhu(produsenBaris(dataSuhu), 30)

for baris in saring do
  print(baris)
end
text Keluaran
sensorA 31
sensorA 33

Penyaring di dalam contoh menggunakan for baris in sumber do, yang berarti fungsi hasil coroutine.wrap langsung digunakan sebagai iterator dalam for generik. Selama penyaring belum menemukan baris yang lolos, penyaring menarik baris berikutnya dari produsen, sehingga produsen hanya dipanggil seperlunya. Diagram berikut menggambarkan aliran data dan aliran kendali antartahap.

Pipeline coroutineProdusenmenghasilkan barisyieldresumePenyaringmemilih baris tertentuyieldresumeKonsumenmencetak hasilpelaksanaan tetap berurutan, satu coroutine berjalan pada satu waktu
Pipeline coroutine dengan tahap produsen, penyaring, dan konsumen

Setiap tahap pada diagram adalah coroutine yang berdiri sendiri, tetapi pelaksanaannya tetap berurutan. Konsumen melanjutkan penyaring dengan pemanggilan fungsi, penyaring melanjutkan produsen bila membutuhkan baris baru, dan kendali kembali mengikuti resume yang sama. Dengan cara ini, penambahan tahap baru cukup ditulis sebagai satu fungsi pembungkus tanpa mengubah tahap lainnya.

Ringkasan#

  • coroutine.create membuat coroutine, coroutine.resume menjalankannya, dan coroutine.yield menunda pelaksanaan sambil mengirim nilai kepada pemanggil.
  • Nilai yang dikirim coroutine.resume diterima coroutine.yield sebagai nilai balik, sehingga dua arah pertukaran data terjadi pada pasangan yang sama.
  • coroutine.status melaporkan suspended, running, normal, atau dead, sedangkan coroutine.wrap membungkus coroutine menjadi fungsi biasa yang siap dipanggil berulang.
  • Coroutine bukan thread paralel, pelaksanaannya kooperatif dan berurutan, serta pola produsen-konsumen dan pipeline dibangun dengan yield sebagai pengirim data.

Referensi#