Elixir - Proses

Di Elixir, semua kode berjalan di dalam proses. Proses diisolasi satu sama lain, berjalan bersamaan satu sama lain dan berkomunikasi melalui penyampaian pesan. Proses Elixir tidak sama dengan proses sistem operasi. Proses di Elixir sangat ringan dalam hal memori dan CPU (tidak seperti utas di banyak bahasa pemrograman lainnya). Karena itu, tidak jarang ada puluhan atau bahkan ratusan ribu proses yang berjalan secara bersamaan.

Dalam bab ini, kita akan belajar tentang konstruksi dasar untuk menghasilkan proses baru, serta mengirim dan menerima pesan di antara proses yang berbeda.

Fungsi Bibit

Cara termudah untuk membuat proses baru adalah dengan menggunakan spawnfungsi. Ituspawnmenerima fungsi yang akan dijalankan dalam proses baru. Misalnya -

pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)

Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -

false

Nilai kembali dari fungsi spawn adalah PID. Ini adalah pengenal unik untuk proses tersebut dan jika Anda menjalankan kode di atas PID Anda, ini akan berbeda. Seperti yang Anda lihat dalam contoh ini, prosesnya mati ketika kami memeriksa untuk melihat apakah masih hidup. Ini karena proses akan keluar segera setelah selesai menjalankan fungsi yang diberikan.

Seperti yang telah disebutkan, semua kode Elixir berjalan di dalam proses. Jika Anda menjalankan fungsi mandiri, Anda akan melihat PID untuk sesi Anda saat ini -

pid = self
 
Process.alive?(pid)

Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -

true

Message Passing

Kami dapat mengirim pesan ke proses dengan send dan menerimanya dengan receive. Mari kita meneruskan pesan ke proses saat ini dan menerimanya pada saat yang sama.

send(self(), {:hello, "Hi people"})

receive do
   {:hello, msg} -> IO.puts(msg)
   {:another_case, msg} -> IO.puts("This one won't match!")
end

Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -

Hi people

Kami mengirim pesan ke proses saat ini menggunakan fungsi kirim dan meneruskannya ke PID diri. Kemudian kami menangani pesan masuk menggunakanreceive fungsi.

Ketika pesan dikirim ke suatu proses, pesan tersebut disimpan di process mailbox. Blok terima melewati kotak surat proses saat ini untuk mencari pesan yang cocok dengan salah satu pola yang diberikan. Blok terima mendukung penjaga dan banyak klausul, seperti kasus.

Jika tidak ada pesan di kotak surat yang cocok dengan salah satu pola, proses saat ini akan menunggu hingga pesan yang cocok diterima. Waktu tunggu juga dapat ditentukan. Sebagai contoh,

receive do
   {:hello, msg}  -> msg
after
   1_000 -> "nothing after 1s"
end

Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -

nothing after 1s

NOTE - Batas waktu 0 dapat diberikan jika Anda sudah mengharapkan pesan tersebut berada di kotak surat.

Tautan

Bentuk pemijahan yang paling umum di Elixir sebenarnya melalui spawn_linkfungsi. Sebelum melihat contoh dengan spawn_link, mari kita pahami apa yang terjadi ketika suatu proses gagal.

spawn fn -> raise "oops" end

Ketika program di atas dijalankan, itu menghasilkan kesalahan berikut -

[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
   :erlang.apply/2

Itu mencatat kesalahan tetapi proses pemijahan masih berjalan. Ini karena proses diisolasi. Jika kita ingin kegagalan dalam satu proses menyebar ke proses lain, kita perlu menghubungkannya. Ini bisa dilakukan denganspawn_linkfungsi. Mari kita pertimbangkan contoh untuk memahami hal yang sama -

spawn_link fn -> raise "oops" end

Ketika program di atas dijalankan, itu menghasilkan kesalahan berikut -

** (EXIT from #PID<0.41.0>) an exception was raised:
   ** (RuntimeError) oops
      :erlang.apply/2

Jika Anda menjalankan ini di iexshell maka shell menangani kesalahan ini dan tidak keluar. Tetapi jika Anda menjalankan dengan terlebih dahulu membuat file script dan kemudian menggunakanelixir <file-name>.exs, proses induk juga akan dihentikan karena kegagalan ini.

Proses dan tautan memainkan peran penting saat membangun sistem toleransi kesalahan. Dalam aplikasi Elixir, kami sering menghubungkan proses kami ke supervisor yang akan mendeteksi ketika suatu proses mati dan memulai proses baru sebagai gantinya. Ini hanya mungkin karena proses diisolasi dan tidak membagikan apa pun secara default. Dan karena proses diisolasi, tidak mungkin kegagalan dalam suatu proses akan merusak atau merusak status proses lainnya. Sementara bahasa lain akan meminta kita untuk menangkap / menangani pengecualian; di Elixir, kami sebenarnya baik-baik saja dengan membiarkan proses gagal karena kami mengharapkan supervisor untuk memulai ulang sistem kami dengan benar.

Negara

Jika Anda membuat aplikasi yang memerlukan status, misalnya, untuk menyimpan konfigurasi aplikasi Anda, atau Anda perlu mengurai file dan menyimpannya di memori, di mana Anda akan menyimpannya? Fungsionalitas proses Elixir dapat berguna saat melakukan hal-hal seperti itu.

Kita dapat menulis proses yang berulang tanpa batas, mempertahankan status, dan mengirim serta menerima pesan. Sebagai contoh, mari kita tulis modul yang memulai proses baru yang berfungsi sebagai penyimpanan nilai kunci dalam file bernamakv.exs.

defmodule KV do
   def start_link do
      Task.start_link(fn -> loop(%{}) end)
   end

   defp loop(map) do
      receive do
         {:get, key, caller} ->
         send caller, Map.get(map, key)
         loop(map)
         {:put, key, value} ->
         loop(Map.put(map, key, value))
      end
   end
end

Perhatikan bahwa file start_link fungsi memulai proses baru yang menjalankan loopfungsi, dimulai dengan peta kosong. Ituloopfungsi kemudian menunggu pesan dan melakukan tindakan yang sesuai untuk setiap pesan. Dalam kasus a:getpesan, itu mengirim pesan kembali ke pemanggil dan loop panggilan lagi, untuk menunggu pesan baru. Selagi:put pesan benar-benar memanggil loop dengan versi baru peta, dengan kunci dan nilai yang diberikan disimpan.

Sekarang mari kita jalankan yang berikut -

iex kv.exs

Sekarang Anda harus berada di iexkulit. Untuk menguji modul kami, coba yang berikut -

{:ok, pid} = KV.start_link

# pid now has the pid of our new process that is being 
# used to get and store key value pairs 

# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}

# Ask for the key :hello
send pid, {:get, :hello, self()}

# Print all the received messages on the current process.
flush()

Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -

"Hello"