{"id":4271,"date":"2014-10-29T09:00:19","date_gmt":"2014-10-29T11:00:19","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=4271"},"modified":"2015-06-01T12:20:16","modified_gmt":"2015-06-01T15:20:16","slug":"playing-with-elixir-and-go-concurrency-models","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2014\/10\/playing-with-elixir-and-go-concurrency-models\/","title":{"rendered":"Playing with Elixir and Go concurrency models"},"content":{"rendered":"

In Go Concurrency Patterns talk<\/a>, Google I\/O 2012, presenter Rob Pike demos some great concurrency features from Go, like channels and Go routines, and how it can be used to build fast, replicated and robust software.<\/p>\n

Concurrency patterns is a very interesting topic but there was one statement in special<\/a> that got me thinking:<\/p>\n

\n “The models are equivalent but express things differently.”\n<\/p><\/blockquote>\n

This is about the distinction between Go channels and its cousin Erlang messages passing<\/a> approach to communicating between processes.<\/p>\n

Implementing Go channels with Elixir processes and messages<\/h2>\n

Before discovering that it has been done before<\/a>, I wrote some simple Elixir code to play with the theoretical equivalence between those two models.<\/p>\n

Notice that this implementation is not meant to be complete, nor efficient and is not recommended for production software. Don\u2019t<\/em> do it at home.<\/p>\n

\ndefmodule GoChannel do\n  def make do\n    spawn(&GoChannel.loop\/0)\n  end\n\n  def write(channel, val) do\n    send(channel, { :write, val})\n  end\n\n  def read(channel) do\n    send(channel, { :read, self })\n\n    receive do\n      { :read, channel, val} -> val\n    end\n  end\n\n  def loop do\n    receive do\n      { :read, caller } -> receive do\n        { :write, val } -> send(caller, { :read, self, val }); loop\n      end\n    end\n  end\nend\n<\/pre>\n

Some tests<\/p>\n

\ndefmodule GoChannelTest do\n  use ExUnit.Case\n\n  test \"write and read to a channel\" do\n    channel = GoChannel.make\n    GoChannel.write(channel, 'hello')\n    assert GoChannel.read(channel) == 'hello'\n  end\n\n  test \"write and read preserves order\" do\n    channel = GoChannel.make\n    GoChannel.write(channel, 'hello')\n    GoChannel.write(channel, 'world')\n    assert GoChannel.read(channel) == 'hello'\n    assert GoChannel.read(channel) == 'world'\n  end\nend\n<\/pre>\n

This pseudo channel implementation relies on a combination of messages between processes to simulate the original FIFO behaviour of channels.<\/p>\n

The same way one could pass a channel as parameter to other functions, since it\u2019s a first-class citizen, we could pass the result of GoChannel.make<\/code>, since it\u2019s a PID<\/code>, which in turn is a first-class citizen in Elixir.<\/p>\n

Back to concurrency patterns<\/h2>\n

The first pattern demonstrated in Rob’s talk was fanIn<\/code>, where two channels were combined into a single one.<\/p>\n

\nfunc fanIn(input1, input2 <-chan string) <-chan string {\n    c := make(chan string)\n    go func() { for { c <- <-input1 } }()\n    go func() { for { c <- <-input2 } }()\n    return c\n}\n<\/pre>\n

We could translate this code to Elixir, using our borrowed abstraction:<\/p>\n

\ndefmodule Patterns do\n  def fan_in(chan1, chan2) do\n    c = GoChannel.make\n\n    spawn(loop(fn -> GoChannel.write(c, GoChannel.read(chan1)) end))\n    spawn(loop(fn -> GoChannel.write(c, GoChannel.read(chan2)) end))\n\n    c\n  end\n\n  defp loop(task) do\n    fn -> task.(); loop(task) end\n  end\nend\n<\/pre>\n

Some tests:<\/p>\n

\ndefmodule PatternsTest do\n  use ExUnit.Case\n\n  test \"fan_in combines two channels into a single one\" do\n    chan1 = GoChannel.make\n    chan2 = GoChannel.make\n\n    c = Patterns.fan_in(chan1, chan2)\n\n    GoChannel.write(chan1, 'hello')\n    GoChannel.write(chan2, 'world')\n\n    assert GoChannel.read(c) == 'hello'\n    assert GoChannel.read(c) == 'world'\n  end\nend\n<\/pre>\n

We could go even further in this mimic game and try to implement the select<\/code> statement, but that would be a very extensive one. First let\u2019s reflect a little about composing more complex functionality with channels.<\/p>\n

Channels as Streams<\/h2>\n

From a consumers perspective, reading from a channel is like getting values out of a stream. So, one could wrap a channel in a stream, using the Stream.unfold\/2<\/code> function:<\/p>\n

\n  def stream(channel) do\n    Stream.unfold(channel,\n                  fn channel -> {read(channel), channel} end)\n  end\n<\/pre>\n

This function returns a Stream<\/code>, which gives us lots of power to compose using its module functions like map\/2<\/code>, zip\/2<\/code>, filter\/2<\/code>, and so on.<\/p>\n

One test to demo that:<\/p>\n

\n  test \"compose channel values with streams\" do\n    channel = GoChannel.make\n    stream = GoChannel.stream(channel)\n\n    GoChannel.write(channel, 1)\n    GoChannel.write(channel, 2)\n    GoChannel.write(channel, 3)\n\n    doubles = Stream.map(stream, &(&1 * 2)) |> Stream.take(2) |> Enum.to_list\n\n    assert doubles == [2, 4]\n  end\n<\/pre>\n

Reviewing comparisons<\/h2>\n

The following quote from Rob Pike's talk is one common analogy used to compare channels and Erlang concurrency models:<\/p>\n

\n \u201cRough analogy: writing to a file by name (process, Erlang) vs. writing to a file descriptor (channel, Go).\u201d\n<\/p><\/blockquote>\n

I think analogies are really useful for communication but I believe they work better as the start of an explanation, not its summarization. So I think we could detail differences a little further.<\/p>\n

For example, PID<\/code>s are not like \u201cfile names\u201d since they are anonymous and automatically generated. As we just saw, PID<\/code> is a first-class citizen and in the language\u2019s perspective, is just as flexible as a channel.<\/p>\n

I would say that channels abstraction reinforce isolation from producer to consumer, in the sense that Go routines writing to a channel doesn't know when nor who is going to consume that information. But it doesn't mean that using processes and messages one could not achieve the same level of isolation, as we just demoed.<\/p>\n

On the other hand, identifying producers and consumers explicitly allow us to monitor and supervise them, allowing language like Erlang and Elixir to leverage the so-called supervision trees useful for building fault-tolerant software in those languages.<\/p>\n

Besides been an interesting exercise to mimic Go\u2019s way of solving problems, one should be aware that Erlang and Elixir have their own abstractions and patterns for handling concurrency.
\nFor example, one could use the GenEvent<\/a> module to implement a pub\/sub functionality.<\/p>\n

Elixir, Erlang and Go have some common goals, like the ones cited in the first paragraph of this post, but they also have their specifics. Embracing differences provides better results in the long term because it helps leverage each language power.<\/p>\n

References<\/h2>\n