spawn dan receive

Pembuatan proses dengan spawn dan penerimaan pesan dengan receive beserta rekursi state.

Daftar isi

Topik ini memperkenalkan pola paling mendasar konkurensi Erlang: membuat proses dengan spawn, menerima pesan dengan receive, dan menjaga proses tetap hidup lewat rekursi dengan state sebagai argumen. Setelah membaca, layanan berupa proses kecil yang mengerjakan permintaan perhitungan dapat dibangun dan dijalankan dari shell.

Membuat proses dengan spawn#

Fungsi spawn/1 membuat proses baru yang menjalankan sebuah fungsi tanpa argumen, lalu langsung mengembalikan PID prosesnya tanpa menunggu apa pun. Proses itu memiliki heap, tumpukan, dan mailbox sendiri, dan berjalan berdampingan dengan proses lain di bawah penjadwal BEAM. Modul berikut membungkus spawn ke dalam fungsi mulai/0, sehingga pemanggil tidak menyentuh spawn langsung:

erlang layan.erl
-module(layan).
-export([mulai/0]).

mulai() -> spawn(fun putar/0).

putar() ->
    receive
        {hitung, A, B, Dari} -> Dari ! {hasil, A + B}, putar();
        berhenti -> ok
    end.

Fungsi putar/0 berisi satu ekspresi receive dengan dua klausa. Klausa pertama menerima tuple permintaan hitung, mengirim hasilnya ke pengirim, lalu memanggil kembali dirinya. Klausa kedua menerima atom berhenti dan selesai tanpa memanggil kembali, sehingga proses berakhir secara normal.

Menjalankan layanan dari shell#

Sesi berikut memulai layanan, mengirim permintaan, dan membaca balasannya:

erlang Membuat layanan dan mengirim permintaan
Erlang/OTP 29 [erts-17.1.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]

Eshell V17.1.1 (press Ctrl+G to abort, type help(). for help)
1> c(layan).
{ok,layan}
2> P = layan:mulai().
<0.92.0>
3> P ! {hitung, 2, 3, self()}.
{hitung,2,3,<0.85.0>}
4> flush().
Shell got {hasil,5}
ok
5> P ! berhenti.
berhenti

Nilai <0.92.0> adalah PID proses layanan, bentuk yang tercetak sebagai tiga bilangan di dalam kurung sudut. Operator ! mengirim pesan tanpa menunggu, dan nilai kembaliannya adalah pesan itu sendiri. Fungsi flush/0 milik shell mencetak seluruh pesan yang menunggu di mailbox proses shell lalu mengosongkannya, sehingga balasan {hasil,5} dari layanan terlihat jelas. Pesan terakhir berhenti membuat proses memilih klausa kedua dan berakhir.

State hidup di argumen rekursi#

Proses layanan membutuhkan state yang bertahan antar pesan, misalnya jumlah permintaan yang pernah dilayani. Erlang tidak memiliki variabel yang berubah, sehingga state dibawa sebagai argumen saat proses memanggil kembali dirinya:

erlang Bentuk umum loop dengan state
putar(N) ->
    receive
        {hitung, A, B, Dari} -> Dari ! {hasil, A + B}, putar(N + 1);
        {jumlah_dilayani, Dari} -> Dari ! N, putar(N);
        berhenti -> ok
    end.

Setiap pemanggilan putar/1 membawa state terkini, dan pembaruan state berarti memanggil diri dengan nilai baru. Bentuk ini terlihat seperti perulangan, dan memang itulah perulangan pada proses: receive menunggu, pesan datang, pekerjaan dikerjakan, lalu panggilan kembali menunggu pesan berikutnya. Karena setiap panggilan itu tail call, memori proses tetap konstan selama layanannya hidup, sebagaimana dijelaskan pada modul Rekursi dan list.

State yang bekerja: penghitung permintaan#

Bentuk putar/1 di atas menjadi nyata pada modul kedua berikut, yaitu penghitung permintaan yang membawa jumlah permintaan sebagai state dan menyediakan fungsi pembantu untuk permintaan-balasan:

erlang penghitung.erl
-module(penghitung).
-export([mulai/0, nilai/1]).

mulai() -> spawn(fun() -> putar(0) end).

putar(N) ->
    receive
        {tambah, Dari} -> Dari ! {ditambah, N + 1}, putar(N + 1);
        {nilai, Dari} -> Dari ! {nilai_kini, N}, putar(N);
        berhenti -> ok
    end.

nilai(P) ->
    P ! {nilai, self()},
    receive {nilai_kini, N} -> N after 5000 -> {error, waktu_habis} end.

State N hanya hidup sebagai argumen putar/1, dan setiap pesan memperbarui atau membacanya. Perhatikan bahwa balasannya diberi tanda: {ditambah, ...} dan {nilai_kini, ...} masing-masing menyertakan nilai state, sehingga penerima tahu persis pesan apa yang harus dibaca. Hasil eksekusinya:

erlang Menambah dan membaca state proses
1> c(penghitung).
{ok,penghitung}
2> K = penghitung:mulai().
<0.92.0>
3> K ! {tambah, self()}.
{tambah,<0.85.0>}
4> K ! {tambah, self()}.
{tambah,<0.85.0>}
5> penghitung:nilai(K).
2

Fungsi nilai/1 mengirim permintaan lalu menunggu balasan bertanda {nilai_kini, N} dengan batas waktu lima detik. Penanda itu bukan hiasan: bila balasannya ditulis ok saja, pembacaan berikutnya akan menangkap pesan yang tidak dimaksud karena mailbox mengandung balasan lama, dan kesalahan semacam ini dibedah pada topik PID dan mailbox.

Penerimaan berulang dan pengirim#

Dua detail pada modul layan perlu diperhatikan karena muncul di hampir semua proses Erlang. Pertama, pemanggilan putar() setelah mengirim balasan membuat proses kembali menunggu. Tanpa panggilan itu, proses mengerjakan satu pesan lalu berakhir dan permintaan berikutnya menggantung tanpa pemberitahuan. Kedua, tuple permintaan membawa self() dari pengirim, yaitu PID proses pemanggil, sehingga layanan tahu ke mana hasil dikirim tanpa perlu konfigurasi tambahan. Pola permintaan-balasan dengan PID pengirim di dalam pesan inilah kontrak dasar antar proses.

Kesalahan umum#

  • Melupakan panggilan kembali di akhir klausa. Proses mengerjakan satu pesan lalu berakhir diam-diam, dan pesan berikutnya menumpuk di mailbox proses yang sudah mati tanpa pesan error ke pengirim. Pesan yang dikirim ke proses mati hilang tanpa jejak.
  • Menganggap spawn menunggu proses siap. Fungsi itu kembali segera dengan PID, dan proses baru mungkin belum sampai pada receive-nya saat pesan pertama dikirim. Tidak masalah, karena pesan menunggu di mailbox sampai receive membacanya.
  • Memakai flush/0 sebagai cara membaca balasan di kode program. Fungsi itu milik shell untuk eksplorasi. Kode produksi memakai receive dengan pola bertanda dan batas waktu.
  • Membaca balasan tanpa penanda dan tanpa batas waktu. Balasan lama yang tertinggal di mailbox tertangkap lebih dulu, dan proses pasangan yang mati membuat receive menggantung selamanya. Pola nilai/1 di atas menutup keduanya.

Ringkasan#

  • spawn/1 membuat proses ringan yang menjalankan sebuah fungsi dan langsung mengembalikan PID-nya.
  • receive menunggu pesan di mailbox dan memilih klausa lewat pencocokan pola, sama seperti case.
  • State proses hidup sebagai argumen pada panggilan kembali di akhir klausa receive, dan bentuk itulah perulangan pada proses.
  • Pola permintaan-balasan membawa self() pengirim di dalam pesan, sehingga layanan mengirim balasan ke tempat yang tepat.

Referensi#