Objectives

  • Gain an understanding of the importance of concurrency
  • Understand how newer languages such as elixir provide features to support concurrent programming

Introduction

Concurrent programming, where a program or programs run multiple threads of execution in parallel, is nothing new. The concepts behind it go back decades, as Operating Systems researchers will atest to. But many programs (and programmers) to this day do not fully exploit the benefits of multithreading due to it's percieved complexity in implementation and debugging.

However, as we approach the end of Moore's law there is a new immpetus to find techniques to keep up with the ever expanding amount of data and performance we expect from modern technology. Languages like Elixir make what is old new again and try to provide tools for programmers to tame concurrency and it's percieved complexity.

Elixir

Work through the Elixir introduction section on processes

Expressions

Open up iex and type the following expressions:

iex> x=spawn(fn -> receive do {pid,x} -> send(pid,x+1) end end)
iex> send x,{self(),3}
iex> flush()
iex> send x,{self(),3}
iex> flush()

Lab questions

  1. write a module with a start/0 method that spawns and returns a process that when sent a tuple (as above), sends back the next number, but that remains active so you can send it multiple messages, and get responses.
  2. write a module with a start/0 method that spawns and returns a process that when sent a tuple (as above), sends back the cumulative sum, that remains active so you can send it multiple messages, and get responses. If you sent it the message send x,{self(),3} 3 times, you would get 3, 6, and 9 sent back to you.
  3. This is a parallel map function:
      def pmap(collection, fun) do
        me = self()
        collection
        |> Enum.map(fn (elem) ->
          spawn_link fn -> (send me, { self(), fun.(elem) }) end end)
        |> Enum.map(fn (pid) ->
          receive do { ^pid, result } -> result end
        end)
      end
    Experiment with it versus the Enum.map function. Define a factorial function and map it over the sequence 1..100. Use Enum.take and Enum.drop to examine portions of the list and check if map and pmap perform the same.
  4. This is a timer function:
      def measure(function) do
        function
        |> :timer.tc
        |> elem(0)
        |> Kernel./(1_000_000)
      end
    You can use it like measure fn -> fact 1000 end. Experiment with timing use of pmap and map (factorials of 2000..3000 is probably a reasonable test. Explain any differences between execution time. Can you infer how many processors your computer has?