3 Underrated Concurrency Models
Topics
Clojure - Flexible single process concurrency
Erlang/Elixir - Distributed, fault tolerant
OpenCL - highly parallel, not so flexible
What is Concurrency?
The ability to be executed out-of-order or in partial order,
without affecting the final outcome
Why Care?
-
Moore's Law is dead
-
Because we can: microservices
pthreads
-
Easy to mess up, impossible to debug
-
Good luck with shared state
-
Poor real world performance
(are not the answer)
Immutability: the FP superpower
let x = 42;
const y = [1,2,3];
y.splice(0,1); // [1]
y // [2, 3]
// woops
But...
-
How do we change things?
-
How do we store changes?
Clojure
-
A "functional" (data oriented) LISP
-
Runs on the JVM (cljs in the browser)
-
Real world language, not an academic BS
-
Was built for single process concurrency
Clojure syntax
(defn add [a b]
(+ a b))
function add(a, b) {
return a + b;
}
Clojure syntax
(filter #{:red :green} [:red :green :blue])
const allowed = new Set(["green", "blue"]);
["red", "green", "blue"].filter(c => allowed.has(c));
How Do We Change Data structs in Clojure?
We Dont!
(-> {:x { :y [ {:z #{"apple"}}]}}
(update-in [:x :y 0 :z] conj "banana"))
;; {:x {:y [{:z #{"apple" "banana"}}]}}
Persistent Data Structures
-
Immutable, share structure
-
Bit partitioned hash tries
-
Same time complexity O(1) = O(log32)
-
Immutable.js is the (unusable) JS port
(map inc (range 0 100))
Free Concurrency
(concat
(map inc (range 0 25))
(map inc (range 25 50))
(map inc (range 50 75))
(map inc (range 75 100)))
map -> pmap
(map inc (range 0 10000000))
(pmap inc (range 0 10000000))
map -> pmap
(map inc (range 0 10000000))
"Elapsed time: 2274.327422 msecs"
(pmap inc (range 0 10000000))
"Elapsed time: 27530.360776 msecs"
(map sleep-100-ms-then-inc (range 0 1000))
(pmap sleep-100ms-then-inc (range 0 1000))
map -> pmap
(map sleep-100-ms-then-inc (range 0 1000))
"Elapsed time: 100195.859968 msecs"
(pmap sleep-100ms-then-inc (range 0 1000))
"Elapsed time: 3218.437153 msecs"
map -> pmap
So how do we save and share changes ?
3 Concurrency Primitives
coordinated | uncoordinated | |
---|---|---|
Sync | Ref | Atom |
Async | Agents |
- Coordinated - can we change multiple values?
- Sync - should we block and wait?
Atoms
(def me
(atom {:name "vitali"
:age 32
:languages #{:clojure :python, :smalltalk}}))
Atoms
(deref me)
{:name "vitali",
:age 32,
:languages #{:smalltalk :clojure :python}}
@me
{:name "vitali",
:age 32,
:languages #{:smalltalk :clojure :python}}
Atoms
;; age
(swap! me update :age inc)
{:name "vitali",
:age 33,
:languages #{:smalltalk :clojure :python}}
;; learn css
(swap! me update :languages conj :css)
{:name "vitali",
:age 33,
:languages #{:smalltalk :clojure :css :python}}
Refs and Clojure's STM
(def player (ref {:health 500 :attack 10 :items #{}}))
(def mob1 (ref {:health 100 :attack 2 :items #{"big-banana"}}))
(def mob2 (ref {:health 100 :attack 4 :items #{"banana"}}))
Refs and Clojure's STM
(defn attack! [attacker enemy]
(dosync
(let [attacker-value (:attack @attacker)
enemy-value (:attack @enemy)]
(alter enemy update :health - attacker-value)
(alter attacker update :health - (/ enemy-value 2)))))
Refs and Clojure's STM
(defn loot! [to from]
(dosync
(when-let [item (first (:items @from))]
(alter to update :items conj item)
(alter from update :items disj item))))
Refs and Clojure's STM
(attack! player mob1)
(attack! player mob2)
(loot! player mob2)
@player
{:health 497, :attack 10, :items #{"banana"}}
@mob2
{:health 90, :attack 4, :items #{}}
@mob1
{:health 90, :attack 2, :items #{"big-banana"}}
Agents
(def sleeper (agent 0))
(send-off sleeper sleep-100ms-then-inc)
@sleeper
=> 0
(await sleeper)
@sleeper
=> 1
The Actor Model
The Actor Model
-
Theoretical model of computation
-
Carl Hewitt 1973
-
Concurrent from the ground up
-
NOT related to Clojure agents!
-
Many Implementations for the actor model
Actor
-
Can spawn new actors
Actor
-
Can spawn new actors
-
Can send messages to other actors
Actor
-
Can spawn new actors
-
Can send messages to other actors
-
Can send messages to himself
Actor
-
Can spawn new actors
-
Can send messages to other actors
-
Can send messages to himself
-
Can store private state
Messages
-
Data (Immutable)
-
The only way to share state
-
Sent to addresses not actors directly
Erlang
-
Was built for real time distributed systems
-
Has it's own VM - BEAM
-
Almost "Pure" Actor Model
-
Developed by Ericsson in 1986
-
Compiles to bytecode for BEAM
-
Much newer (2011)
-
Takes from Ruby and Clojure
-
Has access to the host, like clojure
Elixir Counter
defmodule Counter do
def loop(count) do
receive do
{:next} ->
IO.puts("Current count: #{count}")
loop(count + 1)
end
end
end
counter = spawn(Counter, :loop, [1])
#PID<0.47.0>
iex(2)> send(counter, {:next})
Current count: 1
iex(3)> send(counter, {:next})
Current count: 2
Counter v2.0
defmodule Demo do
def counter() do
receive do
value ->
IO.puts value
Process.sleep(1000)
send(self(), value + 1)
end
counter()
end
end
defmodule Demo do
def ctrl(t_pid) do
receive do
:start ->
t_pid = spawn(&counter/0)
send(t_pid, 0)
ctrl(t_pid)
:stop ->
Process.exit(t_pid, :kill)
ctrl(t_pid)
end
end
def counter() do
receive do
value ->
IO.puts value
Process.sleep(1000)
send(self(), value + 1)
end
counter()
end
end
Erlang VM Killer Features
-
Real time GC
-
Hot code swapping
-
Preemptive multitasking
GPGPU
Data Parallelism
Data Parallelism
(map inc (range 0 10000000))
(pmap inc (range 0 10000000))
GPUs
-
Historically fixed pipline (OpenGL 1)
-
Shaders (OpenGL 2.0) changed the game
-
Many "cores", high latency high throughput
-
SIMD (it's really SPMD) vs CPUs MIMD
Stream processing
-
Steam - a read-only mem (texture - 2D grid)
-
Kernel - a function that runs on a stream
-
Thread - a running kernel instance
(aka "work item")
OpenCL
-
One of the many options for GPGPU
-
Open, not vendor specific
-
Hosted inside a C program
-
Abstracts GPUs and CPUs
OpenCL Code
__kernel void vector_add(__global const int* A,
__global const int* B,
__global int* C)
{
int id = get_global_id(0);
C[id] = A[id] + B[id];
}
OpenCL Execution
-
The actual code is in kernels
-
Executed by many threads (work items)
-
Each work item has an it's own index
-
All work items run the same code (SPMD)
OpenCL device
OpenCL device
OpenCL Context
OpenCL Hardware Abstraction
Compute Unit
PE
OpenCL Memory Regions
-
_private - PE, on chip registers
-
_local - working group
-
_global - context (expensive)
deck
By Vitali Perchonok
deck
- 853