Concurrency

This translation is up to date.

Elixir’in önemli noktalarından biri de eşzamanlılık desteğidir. Erlang VM (BEAM) sayesinde, Elixir’deki eşzamanlılık beklenenden daha kolaydır. Eşzamanlılık modeli, mesaj geçişi yoluyla diğer süreçlerle iletişim kuran aktörlere dayanır.

Bu derste Elixir ile birlikte gelen eşzamanlılık modüllerine bakacağız. Takip eden bölümde, bunları uygulayan OTP davranışlarını ele alıyoruz.

İçerik

Süreçler

Erlang VM’deki işlemler hafiftir ve tüm CPU’larda çalışır. Yerel ileti dizileri gibi görünseler de, daha basittirler ve bir Elixir uygulamasında binlerce eşzamanlı işlemin olması hiçte zor değildir.

Yeni bir süreç oluşturmanın en kolay yolu, adsız veya adlandırılmış bir fonksiyon alan spawn, Yeni bir süreç oluşturduğumuzda, kendi uygulamalarımızda benzersiz bir şekilde tanımlamak için bir Process Identifier veya PID değerini döndürür.

Başlamak için bir modül oluşturacağız ve çalıştırmak istediğimiz bir fonksiyon tanımlayacağız:

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

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

Eş zamansız bir fonksiyon değerlendirmek için spawn/3 kullanırız:

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

İleti Geçişi

İletişim kurmak için süreçler iletilen iletilere güvenir. Bunun iki ana bileşeni var. Bunlar: send/2 ve receive.

send/2 fonksiyonu, PID’lere mesaj göndermemize izin verir. Mesajları dinleyebilmek için receive‘ı kullanın. Eşleşme bulunamazsa, yürütme kesintisiz olarak devam eder.

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

    listen
  end
end

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

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

iex> send pid, :ok
:ok

listen/0 tekrarlı olduğunu fark edebilirsiniz, bu sayede sürecimizin birden çok mesajı ele almasını sağlayabiliriz. Yineleme olmadan, ilk mesajın işlenmesinden sonra işlemimiz kesilirdi.

Süreç bağlantısı

spawn ile ilgili problem, işlemin ne zaman çökeceğini bilmektir. Bunun için süreçlerimizi spawn_link kullanarak bağlamamız gerekiyor.

One problem with spawn is knowing when a process crashes. For that we need to link our processes using spawn_link. İki bağlantılı bir süreç birbirinden aşağıdaki örnektede görebileceğiniz gibi bir çıkış bildirimi alır:

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

Bazen bizim bağlantılı sürecimizin şu anki çöküşünü istemiyoruz. Bunun için Process.flag/2 kullanarak çıkışları yakalamamız gerekiyor. trap_exit bayrağı için erlang’ın process_flag/2 fonksiyonunu kullanabiliriz. Çıkışları yakalarken (trap_exit değeri true olarak ayarlanır), çıkış sinyalleri bir demet mesajı olarak şu şekilde alınacaktır: {: 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

Süreç İzleme

Ya iki süreci birbirine bağlamak istemiyorsak, yine de haberdar edilmek istiyorsak? Bunun için spawn_monitor ile süreci izleyebiliriz. Bir süreci izlediğimizde, sürecin çökmesi veya sürecin beklenmedik bir biçimde çökmesi ile bir ileti alırız.

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

Ajanlar

Ajanlar durumları koruyan arka plan süreçleri etrafında uygulanan bir soyutlamadır. Uygulamalarımızdaki ve düğümümüzdeki diğer süreçlerden onlara erişebiliriz. Ajanımızın durumu, fonksiyonumuzun geri dönüş değerine ayarlanır:

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]

Bir Ajan ismini verdiğimiz zaman, PID’in yerine buna başvurabiliriz:

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

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

Görevler

Görevler, arka planda bir fonksiyonu yürütmek ve daha sonra geri dönüş değerini almak için size bir yol sağlar. İşlem yükü çok olan durumları ele almamız gerekirse özellikle görevler oldukça işinize yarayacaktır.

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