Task dan Channel

Coroutine kooperatif berupa Task beserta pertukaran pesan antar Task melalui Channel.

Daftar isi

Julia menjalankan banyak pekerjaan pada satu aliran kontrol melalui Task, yaitu coroutine kooperatif yang menunda dan melanjutkan eksekusinya atas kehendaknya sendiri. Nilai dipertukarkan antar Task melalui Channel yang aman terhadap thread. Topik ini membahas pembuatan Task, penjadwalan dan pengambilan hasilnya, antrean Channel dengan operasi put! dan take!, serta pola produsen-konsumen yang menjadi dasar pemrograman asinkron. Setelah topik ini dipraktikkan, pekerjaan yang menunggu sumber luar dapat ditulis tanpa memblokir seluruh program.

Task sebagai coroutine kooperatif#

Task adalah unit pekerjaan yang eksekusinya tidak segera dijalankan saat dibuat. Pemanggilan @task ekspresi membungkus ekspresi menjadi objek Task dalam keadaan belum berjalan, dan ekspresi itu baru dieksekusi ketika Task dijadwalkan. Karakter kooperatif berarti peralihan antar Task terjadi pada titik yang dinyatakan secara eksplisit, misalnya saat Task menunggu nilai atau menyerahkan kontrol, bukan dipotong oleh penjadwal waktu sebagaimana thread sistem operasi.

julia Contoh: siklus hidup sebuah Task
t = @task begin
    println("tugas berjalan")
    return 7 * 6
end
println("belum berjalan: ", istaskdone(t))
schedule(t)
wait(t)
println("sudah selesai: ", istaskdone(t))
println("hasil: ", fetch(t))
text Keluaran
belum berjalan: false
tugas berjalan
sudah selesai: true
hasil: 42

Pada contoh di atas, schedule menempatkan Task pada antrean penjadwal sehingga badannya dieksekusi, wait menunggu hingga selesai, dan fetch mengembalikan nilai baliknya. Baris tugas berjalan muncul setelah pemanggilan schedule karena eksekusi badan Task baru dimulai ketika Task mendapat giliran dari penjadwal. Task yang melempar error menyimpan error tersebut, dan fetch melemparkannya kembali kepada pemanggil, sehingga kegagalan pada Task tidak menghilang begitu saja.

Menangguhkan dan melanjutkan#

Sifat kooperatif terlihat jelas pada fungsi yang menyerahkan kontrol. Pemanggilan yield menghentikan eksekusi Task saat ini untuk sementara dan memberi kesempatan Task lain yang sudah dijadwalkan untuk berjalan. Pola ini memungkinkan pekerjaan panjang dipecah tanpa membuat program kehilangan daya tanggap, sebab setiap potongan pekerjaan menyerahkan kontrol pada titik yang aman.

julia Contoh: dua potongan pekerjaan yang bergantian
t = @task begin
    println("tahap 1 selesai")
    yield()
    println("tahap 2 selesai")
end
schedule(t)
println("kontrol kembali ke pemanggil")
wait(t)
text Keluaran
kontrol kembali ke pemanggil
tahap 1 selesai
tahap 2 selesai

Urutan keluaran pada contoh di atas menunjukkan mekanisme penangguhan secara langsung. schedule menempatkan Task pada antrean tanpa menjalankannya seketika, sehingga baris pemanggil tercetak lebih dahulu. Pada wait, Task berjalan hingga mencapai yield(), lalu sisa badan Task dijalankan sebelum wait kembali. Perilaku ini berbeda dari pemanggilan fungsi biasa, karena baris setelah yield() tidak dieksekusi dalam bingkai pemanggilan yang sama, melainkan pada penjadwalan berikutnya.

Channel sebagai antrean pesan#

Channel adalah antrean berkapasitas tetap yang aman dipakai bersama oleh banyak Task maupun thread. put! menambahkan nilai ke antrean dan menunggu bila kapasitas penuh, sedangkan take! mengambil satu nilai dan menunggu bila antrean kosong. Tipe elemen dan kapasitasnya dinyatakan saat pembuatan, misalnya Channel{Int}(4) untuk antrean bilangan bulat berkapasitas empat, dan close menandai bahwa tidak ada nilai baru yang akan masuk.

julia Contoh: menulis dan membaca Channel
kanal = Channel{Int}(4)
for i in 1:4
    put!(kanal, i * i)
end
close(kanal)
println(collect(kanal))
println(isready(kanal))
text Keluaran
[1, 4, 9, 16]
false

Contoh di atas memperlihatkan siklus lengkap satu Channel. Nilai yang dikumpulkan collect mengikuti urutan put! karena Channel menjamin urutan masuknya (first-in, first-out), dan isready mengembalikan false setelah isi antrean habis, sekalipun Channel sudah ditutup. Iterasi for v in kanal juga dapat dipakai sebagai pembaca, dan iterasi tersebut berhenti dengan rapi setelah Channel ditutup dan isinya habis, sehingga pola penutupan ini menjadi sinyal berakhirnya aliran data.

Produsen dan konsumen#

Kombinasi Task dan Channel menghasilkan pola produsen-konsumen (producer-consumer), yaitu satu sisi menghasilkan nilai secara berkelanjutan dan sisi lain memprosesnya segera setelah tersedia. Makro @sync menunggu seluruh blok @async di dalamnya selesai, sehingga program utama tidak berhenti sebelum kedua sisi selesai bekerja.

julia Contoh: pola produsen-konsumen
hasil = Int[]
kanal = Channel{Int}(32)
@sync begin
    @async begin
        for i in 1:5
            put!(kanal, i * 10)
        end
        close(kanal)
    end
    @async begin
        for v in kanal
            push!(hasil, v)
        end
    end
end
println(hasil)
text Keluaran
[10, 20, 30, 40, 50]

Produsen pada contoh menghasilkan lima nilai lalu menutup Channel, sedangkan konsumen membaca hingga Channel ditutup dan isinya habis. Hasil akhirnya terurut pasti karena hanya ada satu produsen dan satu konsumen, dan Channel menjamin urutan pesannya. Apabila sisi produsen menulis berkas atau membaca sensor, konsumen dapat mulai bekerja pada data pertama tanpa menunggu data terakhir tersedia, dan itulah keuntungan utama pemisahan kedua peran tersebut.

Kesalahan umum#

Kesalahan pertama adalah lupa menutup Channel dari sisi produsen. Konsumen yang berbentuk for v in kanal menunggu nilai berikutnya tanpa batas waktu, sehingga program menggantung tanpa pesan error. Perbaikannya adalah menutup Channel tepat setelah nilai terakhir dihasilkan, sebagaimana blok produsen pada contoh sebelumnya.

Kesalahan kedua adalah mengasumsikan Task berjalan segera setelah dibuat. Task baru dieksekusi setelah dijadwalkan, sehingga kode yang membaca hasil Task sebelum fetch atau wait dipanggil menemui nilai yang belum ada. Kesalahan ketiga adalah menaruh beban tunggu pada satu Task saja, misalnya konsumen yang membaca satu nilai lalu menunggu lama sebelum membaca berikutnya, sehingga kapasitas Channel penuh dan produsen ikut menunggu. Antrean berkapasitas cukup dan konsumen yang mengosongkannya secara berkelanjutan menjaga aliran tetap lancar.

Ringkasan#

  • Task adalah coroutine kooperatif yang eksekusinya dimulai lewat schedule atau fetch, dan nilainya diambil dengan fetch setelah selesai.
  • yield menyatakan titik penyerahan kontrol sehingga Task lain yang terjadwal dapat berjalan.
  • Channel adalah antrean aman thread dengan urutan masuk terjaga. Operasi put!, take!, dan close membentuk pola produsen-konsumen yang hasilnya terurut pasti bila hanya ada satu produsen dan satu konsumen.

Referensi#