Task dan rendezvous

Task sebagai aktivitas konkuren bawaan bahasa yang bertemu melalui entry.

Daftar isi

Ada menjadikan konkurensi sebagai bagian bahasa, sehingga program dengan beberapa aliran eksekusi ditulis dengan sintaks biasa tanpa library tambahan. Topik ini membahas task, yaitu unit eksekusi yang berjalan bersamaan dengan aliran utama program, beserta komunikasi antar task melalui entry dan mekanisme rendezvous, yakni pertemuan dua task untuk bertukar parameter. Setelah topik ini dibaca, program dengan beberapa task yang saling bekerja dapat ditulis dan dijalankan langsung dengan GNAT.

Deklarasi dan aktivasi task#

Sebuah task dinyatakan pada bagian deklaratif, sedangkan alur eksekusinya ditulis pada badan task sebagaimana badan subprogram. Task yang tidak memiliki entry boleh dinyatakan sebagai instance tunggal tanpa spesifikasi, yaitu task Pelapor; yang diikuti badannya. Apabila beberapa task dengan perilaku sama diperlukan, task type digunakan dan setiap instance dideklarasikan seperti variabel biasa.

ada Contoh: task type dengan dua instance
-- tidak-dijalankan
task type Sensor is
   entry Ukur (Nilai : out Integer);
end Sensor;

Sensor_A : Sensor;
Sensor_B : Sensor;

Setiap task mulai dieksekusi pada saat aktivasi, yaitu ketika eksekusi blok yang membawanya mencapai begin. Aktivasi seluruh task selesai sebelum aliran utama mengeksekusi pernyataan pertamanya. Program utama tidak memerlukan pernyataan tunggu tersendiri karena bahasa menetapkan bahwa penyelesaian subprogram utama menunggu seluruh task bergantungnya berhenti. Aturan itulah yang membuat contoh pada topik ini selalu menampilkan seluruh keluarannya sebelum program berakhir.

Pilihan antara task type dan instance tunggal mengikuti jumlah task yang dibutuhkan. Instance tunggal cocok untuk task yang hanya ada satu, misalnya pencatat log atau antarmuka perangkat keras, sedangkan task type cocok untuk task yang berulang, misalnya delapan sensor dengan perilaku sama. Setiap instance task type mengeksekusi badan task secara mandiri, dan state miliknya tersimpan pada variabel lokal badan task itu. Komunikasi antar instance tetap melalui entry, sehingga tidak ada dua task yang berbagi variabel secara langsung.

Entry dan rendezvous#

Pada sebuah task, entry adalah titik tunggu yang dapat dipanggil oleh task lain, dinyatakan pada spesifikasi task, dan diterima dengan pernyataan accept di dalam badannya. Panggilan entry memicu rendezvous, yaitu pertemuan antara pemanggil dan pemilik entry. Pemanggil menunggu sampai aksi di dalam accept selesai, sedangkan pemilik entry menunggu apabila belum ada panggilan yang datang. Parameter dipertukarkan pada saat pertemuan itu, baik parameter in menuju pemilik entry maupun parameter out menuju pemanggil. Rendezvous sekaligus menjadi jalur komunikasi dan titik sinkronisasi karena kedua task terikat pada aksi yang sama.

Program berikut mendeklarasikan task Pencatat dengan entry Catat, dan program utama memanggil entry tersebut tiga kali dengan pesan yang berbeda.

ada pencatatan.adb
with Ada.Text_IO;

procedure Pencatatan is
   task Pencatat is
      entry Catat (Pesan : String);
   end Pencatat;

   task body Pencatat is
   begin
      for I in 1 .. 3 loop
         accept Catat (Pesan : String) do
            Ada.Text_IO.Put_Line ("catatan" & Integer'Image (I) & ": " & Pesan);
         end Catat;
      end loop;
   end Pencatat;
begin
   Pencatat.Catat ("sensor suhu alat A");
   Pencatat.Catat ("sensor suhu alat B");
   Pencatat.Catat ("sensor suhu alat C");
end Pencatatan;
bash
# kompilasi program
gnatmake -q pencatatan.adb
# jalankan program
./pencatatan
text Keluaran
catatan 1: sensor suhu alat A
catatan 2: sensor suhu alat B
catatan 3: sensor suhu alat C

Task Pencatat mencapai accept lebih dahulu dan menunggu di situ, sedangkan program utama mengantarkan pesan satu per satu. Setiap panggilan membentuk satu rendezvous, sehingga baris keluaran tercetak berurutan sesuai urutan panggilannya. Setelah perulangan pada badan task selesai, task Pencatat berhenti dan program utama pun berakhir.

Rendezvous antar dua task#

Rendezvous tidak terbatas pada komunikasi antara program utama dan tasknya. Dua task dapat berperan sebagai pemanggil dan pemilik entry sehingga pembagian kerjanya jelas, misalnya satu task mengolah data dan satu task menyajikan hasilnya. Program berikut memasangkan task Analis yang menghitung nilai suhu dengan task Pelapor yang meminta nilai tersebut melalui entry Ambil.

ada aliran_data.adb
with Ada.Text_IO;

procedure Aliran_Data is
   task Analis is
      entry Ambil (Nilai : out Integer);
   end Analis;

   task body Analis is
      Suhu : Integer := 21;
   begin
      for I in 1 .. 3 loop
         Suhu := Suhu + I;
         accept Ambil (Nilai : out Integer) do
            Nilai := Suhu;
         end Ambil;
      end loop;
   end Analis;

   task Pelapor;

   task body Pelapor is
   begin
      for I in 1 .. 3 loop
         declare
            Suhu : Integer;
         begin
            Analis.Ambil (Suhu);
            Ada.Text_IO.Put_Line ("pembacaan" & Integer'Image (I) & ":"
                                  & Integer'Image (Suhu) & " derajat");
         end;
      end loop;
   end Pelapor;
begin
   null;
end Aliran_Data;
bash
# kompilasi program
gnatmake -q aliran_data.adb
# jalankan program
./aliran_data
text Keluaran
pembacaan 1: 22 derajat
pembacaan 2: 24 derajat
pembacaan 3: 27 derajat

Task Analis menghitung nilai berikutnya lebih dahulu, kemudian menunggu pada accept Ambil. Task Pelapor memanggil entry itu tiga kali dan mencetak setiap hasil begitu nilai tersebut diterima. Hanya Pelapor yang mencetak, sehingga keluaran tidak bercampur antar task dan urutannya pasti. Nilai diserahkan satu per satu melalui parameter out, dan kedua task berhenti setelah perulangan ketiga selesai. Pola produsen dan konsumen semacam ini menjadi dasar bagi jalur data yang lebih panjang dengan beberapa task di dalamnya.

Kesalahan umum#

Empat kekeliruan berikut sering muncul saat task dan rendezvous dipraktikkan, dan setiap butir menyertakan perbaikannya.

  • Memanggil entry task yang sudah berhenti. Panggilan semacam ini memicu Program_Error pada saat program berjalan, dengan pesan yang menunjuk baris panggilannya. Atur siklus hidup kedua task agar pemanggil tidak berhenti lebih dulu daripada pemilik entry.
  • Membentuk deadlock dengan panggilan silang. Apabila task A menunggu entry milik task B, sedangkan task B menunggu entry milik task A, keduanya menunggu tanpa batas sehingga tidak ada eksekusi yang berlanjut. Urutkan panggilan sehingga salah satu task menyelesaikan perannya lebih dahulu, atau pindahkan titik temu tersebut ke protected object, yaitu unit yang menjamin akses eksklusif atas data bersama.
  • Mengira program utama perlu menunggu secara manual. Penyelesaian subprogram utama sudah menunggu seluruh task bergantungnya, sehingga perulangan tunggu tambahan hanya mengulang penantian yang sudah dijamin bahasa.
  • Mengaitkan data task dengan variabel global. State task tersimpan pada variabel lokal badannya, sedangkan data yang dibagi antar task harus melalui protected object agar aksesnya tetap eksklusif.

Ringkasan#

  • Task adalah unit konkuren bawaan bahasa yang diaktifkan pada saat blok yang membawanya mulai dieksekusi, dan program utama menunggu seluruh task bergantungnya berhenti.
  • Entry dinyatakan pada spesifikasi task dan diterima dengan accept, sedangkan rendezvous menyalurkan parameter serta menyinkronkan kedua task yang terlibat.
  • Panggilan entry ke task yang telah berhenti memicu Program_Error, dan panggilan silang tanpa pengurutan membentuk deadlock.

Referensi#