Modern concurrency:

Erlang, Scala, Go, Clojure

by Alexey Kachayev, 2013

About me


  • Position: CTO at Kitapps Inc.
  • Production experience: Python, Java, JS, Go, Scala, Clojure, Erlang
  • Looked at: Haskell, Lisp, Scheme


Where to find?

What we are going to talk about?



  • More about concurrency, (much!) less about parallelism
  • Tasks: from practice and from theory
  • Classical approach and problems: threads/locks/mutex
  • One task, different solutions: Actors, Channels, STM
  • What to choose and how to live with all this stuff?
  • p.s. Why functional programming matters

Concurrency is not parallelism


Rob Pike:

Concurrency is the composition of independently executing computations. Concurrency is a way to structure software, ...

Video "Concurrency is not parallelism"

Tasks


  • Theory: Sleeping barber problem
  • Practice: Cache manager for image viewer (you want to show several images on a mobile screen downloaded from a server, but you have hard limits on free memory and disk space)



- Why should we solve this stupid theoretical task?
- Cause any task from practice is much harder

Need more tasks from practice? Ping me after the talk.

Hah, it's easy. Use locks, Luke!


"Classical" approach, each thread:

  • check resource on disk
  • "lock" hash map, check if someone is downloading the same, "unlock"
  • check HTTP header for Content-Length
  • "lock" free space counter, try to subtract size, "unlock"
  • if successful, read from the HTTP body to disk
  • if not, call somebody who will remove old file(s)

Hah, it's easy. Use locks, Luke!


Hmmm, really?

  • What to do with crashes while downloading images?
  • What to do with "waiters" and how to manage free space on concurrent +/- operations?
  • How to manage free space more "intelligently": 10 * 1 mb is better that 1 * 10 mb?
  • How to "kill" a task if an image is not needed (before it's downloaded)?
  • How will this program work if we run 1000 "downloaders"?
  • Are you sure that your locks are good enough???

What is the problem with locks?


Jonas Boner, about fault-tolerant scaling:

  • Locks do not compose
  • Locks breaks encapsulation (you need to know a lot!)
  • Taking too few locks
  • Taking too many locks
  • Taking the wrong locks
  • Taking locks in wrong order
  • Error recovery is hard

What is the problem with locks?


  • It's not the nature of our world!!! (we don't ask the world to stop to do something)
  • What to do with crashes in a critical section?
  • Impossible to test: you will know about the problem only "in production"

Modern concurrency




Think about it! You work in a big free-space office with many programmers (hundreds), you have one board with a piece of paper pinned to it. Each time someone finishes doing a task, he or she should increase the number written on this paper. What options do you have? How will you organize everything? What will you do with paranoid manager who wants to know how many tasks are already done (from time to time, often)?

Modern concurrency


  • Actors (message passing)
  • STM (Software Transactional Memory)
  • Dataflow Concurrency (your homework)

What does actor looks like? (Theory)


  • Separated isolated lightweight processes (actor)
  • Share nothing, avoid side-effects (FP)
  • Pure message passing communication (mailbox)
  • Location transparency

What does actor looks like? (Erlang)


gist

What does actor looks like? (Erlang)


gist

What does actor looks like? (Scala)



  • Very close to previous
  • Approaches for state handling (rough, in short words): actor is a state (Scala) VS. passing state as an argument (Erlang)

Go Channels


gist

Go Channels


gist

Go Channels


"Rough analogy: writing to a file by name (process, Erlang) vs. writing to a file descriptor (channel, Go)." (c) Rob Pike



"Rough analogy: Assume Go channels as portable actor mailboxes" (c) Me

Clojure concurrency primitives


  • Designed for concurrency
  • Identity and value are different things
  • Different approaches for sharing changing state
  • Software transactional memory - synchronous and coordinated approach (memory as database, atomicity, consistency, isolation)
  • Agent(s) - asynchronous and independent approach
  • Atom(s) - synchronous and independent approach

Clojure concurrency primitives


gist

Clojure concurrency primitives


gist

Clojure concurrency primitives


Why functional programming matters in Clojure:


  • Side-effect free functions
  • Deterministic functions/calculations

How to live with this


- I can write the same as Scala/Clojure/Go in Java/Python/..!
- hah, really...?



Everything that can go wrong will go wrong (c) Murphy

How to live with this


Question: Why does adding concurrency slow down this golang code?


Answer: The issue seems to come from your use of rand.Float64(), which uses a shared global object with a Mutex lock on it.

What to choose


  • There is no silver bullet
  • Use most natural approach for your technology / OP (there are many STM implementations, but in Clojure it's idiomatic and part of the core)
  • Look deeply into your domain (presenters usually use most convenient case(s) to tell about functionality)
  • Use "unusual" approach but remember that on this way only conventions rule
  • If you need distributed concurrency - look at actors

What to choose


For Scala developers from Jonas Boner:

  • Start with deterministic, declarative, immutable core
  • Add indeterminism selectively (actors)
  • Add mutability selectively (STM)
  • Finally: add locks and explicit threads

Any questions?




Thanks for your attention