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.
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))belum berjalan: false
tugas berjalan
sudah selesai: true
hasil: 42Pada 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.
t = @task begin
println("tahap 1 selesai")
yield()
println("tahap 2 selesai")
end
schedule(t)
println("kontrol kembali ke pemanggil")
wait(t)kontrol kembali ke pemanggil
tahap 1 selesai
tahap 2 selesaiUrutan 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.
kanal = Channel{Int}(4)
for i in 1:4
put!(kanal, i * i)
end
close(kanal)
println(collect(kanal))
println(isready(kanal))[1, 4, 9, 16]
falseContoh 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.
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)[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
scheduleataufetch, dan nilainya diambil denganfetchsetelah selesai. yieldmenyatakan titik penyerahan kontrol sehingga Task lain yang terjadwal dapat berjalan.- Channel adalah antrean aman thread dengan urutan masuk terjaga. Operasi
put!,take!, danclosemembentuk pola produsen-konsumen yang hasilnya terurut pasti bila hanya ada satu produsen dan satu konsumen.
Referensi#
- Asynchronous Programming — Julia manual — Dokumentasi resmi Task, penjadwalan, dan pemrograman asinkron Julia.
- Parallel Computing — Julia manual — Panduan Channel dan pola komunikasi antar tugas.
- Tasks and Channels — Julia manual — Rujukan API lengkap makro dan fungsi pada modul Base.Task.