Do you want to pick up from where you left of?
Take me there

Keserempakan

Salah satu faktor pelaris Elixir ialah sokongan kepada keserempakan(concurrency). Terima kasih kepada Erlang VM (BEAM), keserempakan di dalam Elixir adalah lebih mudah dari yang dijangkakan. Model keserempakan bergantung kepada Actor, satu proses yang berkomunikasi dengan proses-proses lain melalui pengagihan mesej.

Di dalam pelajaran ini kita akan lihat modul-modul keserempakan yang didatangkan dengan Elixir. Di dalam bab seterusnya kita lihat kelakuan OTP yang melaksanakan mereka

Proses

Proses-proses di dalam Erlang VM adalah ringan dan memanafaatkan kesemua CPU apabila dijalankan. Walaupun mereka nampak seperti ‘native thread’, mereka sebenarnya adalah lebih ringkas dan ianya tidak janggal jika terdapat ribuan proses yang berjalan secara keserempakan di dalam aplikasi Elixir.

Cara paling mudah untuk membuat satu proses baru ialah menggunakan spawn, yang mengambil satu fungsi tanpa nama atau fungsi bernama. Apabila kita membuat satu proses baru ia akan memulangkan satu Process Identifier, atau PID, sebagai pengenalan unik untuk proses itu di dalam aplikasi kita.

Untuk bermula kita akan membuat satu modul dan takrifkan satu fungsi yang kita mahu jalankan:

defmodule Example do
  def add(a, b) do
    IO.puts(a + b)
  end
end

iex> Example.add(2, 3)
5
:ok

Untuk menjalankan fungsi tersebut secara tak segerak(asynchronous) kita gunakan spawn/3:

iex> spawn(Example, :add, [2, 3])
5
#PID<0.80.0>

Pengagihan Mesej

Untuk berkomunikasi, proses-proses bergantung kepada pengagihan mesej. Terdapat dua komponen penting di dalam pengagihan mesej, send/2 dan receive/2. Fungsi send/2 mengupayakan kita untuk menghantar mesej kepada PID. Untuk mengawasi penerimaan mesej, kita gunakan receive.

defmodule Example do
  def listen do
    receive do
      {:ok, "hello"} -> IO.puts("World")
    end
  end
end

iex> pid = spawn(Example, :listen, [])
#PID<0.108.0>

iex> send pid, {:ok, "hello"}
World
{:ok, "hello"}

iex> send pid, :ok
:ok

Perangkaian Proses

Fungsi spawn mempunyai satu masalah iaitu untuk mengetahui apabila sesatu proses itu runtuh(crash). Untuk itu kita perlu untuk merangkaikan proses-proses kita dengan menggunakan spawn_link. Dua proses yang dirangkaikan akan menerima ‘exit notification’ dari satu sama lain:

defmodule Example do
  def explode, do: exit(:kaboom)
end

iex> spawn(Example, :explode, [])
#PID<0.66.0>

iex> spawn_link(Example, :explode, [])
** (EXIT from #PID<0.57.0>) evaluator process exited with reason: :kaboom

Kadang-kadang kita tidak mahu proses yang dirangkaikan meruntuhkan proses sedia ada. Untuk itu kita perlu untk memerangkap ‘exit’ tersebut. ‘Exit’ yang diperangkap akan diterima sebagai satu mesej dalam bentuk tuple: {:Exit, from_pid, reason}.

defmodule Example do
  def explode, do: exit(:kaboom)

  def run do
    Process.flag(:trap_exit, true)
    spawn_link(Example, :explode, [])

    receive do
      {:EXIT, from_pid, reason} -> IO.puts("Exit reason: #{reason}")
    end
  end
end

iex> Example.run
Exit reason: kaboom
:ok

Pengawasan Proses

Bagaiana pula jika kita tidak mahu merangkaikan dua proses tetapi masih diberitahu mengenai keadaan semasa proses-proses tersebut? Untuk itu kita boleh membuat pengawasan proses dengan menggunakan spawn_monitor. Apabila mengawasi satu proses, kita akan mendapat satu mesej jika proses tersebut runtuh tanpa meruntuhkan sama proses semasa kita atau tanpa memerlukan ‘exit’ itu diperangkap.

defmodule Example do
  def explode, do: exit(:kaboom)

  def run do
    {pid, ref} = spawn_monitor(Example, :explode, [])

    receive do
      {:DOWN, ref, :process, from_pid, reason} -> IO.puts("Exit reason: #{reason}")
    end
  end
end

iex> Example.run
Exit reason: kaboom
:ok

Agent

Agent adalah sejenis peniskalaan(abstraction) proses-proses ‘background’ yang menyelenggara keadaan semasa(state). Mereka boleh dicapai daripada proses-proses lain dari dalam aplikasi dan nod kita. Keadaan semasa(state) Agent kita ditetapkan kepada nilai yang dipulangkan oleh fungsi-fungsi kita:

iex> {:ok, agent} = Agent.start_link(fn -> [1, 2, 3] end)
{:ok, #PID<0.65.0>}

iex> Agent.update(agent, fn (state) -> state ++ [4, 5] end)
:ok

iex> Agent.get(agent, &(&1))
[1, 2, 3, 4, 5]

Apabila kita menamakan satu Agent, ia boleh dirujuk sebagai Agent dan tidak melalui PID-nya.

iex> Agent.start_link(fn -> [1, 2, 3] end, name: Numbers)
{:ok, #PID<0.74.0>}

iex> Agent.get(Numbers, &(&1))
[1, 2, 3]

Task

‘Task’ menyediakan satu cara untk menjalankan satu fungsi di ‘background’ dan menerima nilai yang dipulangkan kemudian. Ianya amat berguna apabila menguruskan operasi-operasi yang berat tanpa merencatkan perjalanan aplikasi.

defmodule Example do
  def double(x) do
    :timer.sleep(2000)
    x * 2
  end
end

iex> task = Task.async(Example, :double, [2000])
%Task{pid: #PID<0.111.0>, ref: #Reference<0.0.8.200>}

# Do some work

iex> Task.await(task)
4000
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!