Slide 1

Slide 1 text

Many Ways to Concur Abhinav Sarkar Functional Conf / Bengaluru, 2018

Slide 2

Slide 2 text

Source: Wikipedia

Slide 3

Slide 3 text

Concurrency

Slide 4

Slide 4 text

Concurrency is a program- structuring technique in which there are multiple threads of control which execute "at the same time". — Simon Marlow, Parallel and Concurrent Programming in Haskell

Slide 5

Slide 5 text

Threads » A sequence of instructions along with a context. » Run by processors. » Managed by schedulers. Source: Wikipedia

Slide 6

Slide 6 text

Green Threads » Green threads are scheduled by the runtime system or the virtual machine instead of the OS kernel. » Starting green threads is cheaper and faster. Context switches are faster too. » Making system calls blocks the thread. So the runtime needs to support asynchronous IO. » Cannot exploit multiprocessor parallelism in their simplest form.

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Synchronization

Slide 9

Slide 9 text

Synchronization » The process by which multiple threads agree (or concur) on something. » Locks prevent concurrent access to critical sections/memory. » Locks do not compose.

Slide 10

Slide 10 text

Synchronization » Lock-free shared-state concurrency » Software Transactional Memory » Communicating Sequential Processes » No shared-state concurrency » Actor Model

Slide 11

Slide 11 text

Actor Model

Slide 12

Slide 12 text

Actor Model » An actor is an entity which can » create new actors » send messages to actors » behaves in a certain way on receiving a message

Slide 13

Slide 13 text

Source: cwiki.apache.org/confluence/display/FLINK/Akka+and+Actors

Slide 14

Slide 14 text

-module(echo). -export([loop/0]). loop() -> receive quit -> io:fwrite("Bye~n"); Num -> io:fwrite("Received: ~p~n", [Num]), loop() end. > Pid = spawn(fun echo:loop/0). > Pid ! 232. Received: 232 > Pid ! quit. Bye

Slide 15

Slide 15 text

-module(counter). -export([loop/1]). loop(N) -> receive {inc} -> loop(N+1); {get, Sender} -> Sender ! N, loop(N) end. > Pid = spawn(counter, loop, [0]). > Pid ! {inc}. > Pid ! {get, self()}. > receive Value -> io:fwrite("~p~n", [Value]) end. 1 > Pid ! {getx, self()}. > receive Value -> io:fwrite("~p~n", [Value]) > after 1000 -> io:fwrite("Timeout~n") end. Timeout

Slide 16

Slide 16 text

+----------------------------------+ | | | PID / Status / Registered Name | Process | | Control | Initial Call / Current Call +----> Block | | (PCB) | Mailbox Pointers | | | +----------------------------------+ | | | Function Parameters | | | Process | Return Addresses +----> Stack | | | Local Variables | | | +-------------------------------+--+ | | | | ^ v +----> Free | | | Space +--+-------------------------------+ | | | Mailbox Messages (Linked List) | | | Process | Compound Terms (List, Tuples) +----> Private | | Heap | Terms Larger than a word | | | +----------------------------------+

Slide 17

Slide 17 text

Erlang Actors » Pros: » Communication is synchronization. » No need to worry about mutual exclusion. » Models distributed systems very well. » Cons: » Can be slower compared to Shared-state Concurrency. » Multi entity consensus is difficult to achieve. » Mailboxes may overflow if the message processing is slow.

Slide 18

Slide 18 text

Software Transactional Memory

Slide 19

Slide 19 text

Software Transactional Memory » Software transactional memory (STM) allows changing multiple mutable variables together in a single atomic operation. » Atomicity: All the state changes in an atomic operation become visible too all the threads at once. » Isolation: The atomic operation is completely unaffected by whatever other threads are doing.

Slide 20

Slide 20 text

data STM a -- abstract instance Monad STM -- among other things data TVar a -- abstract newTVar :: a -> STM (TVar a) readTVar :: TVar a -> STM a writeTVar :: TVar a -> a -> STM () atomically :: STM a -> IO a retry :: STM a orElse :: STM a -> STM a -> STM a

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Account { private int id, amount; private Lock lock = new ReentrantLock(); Account(int id, int initialAmount) { this.id = id; this.amount = initialAmount; } public void withdraw(int n) { this.lock.lock(); try { this.amount -= n; } finally { this.lock.unlock(); } } public void deposit(int n) { this.withdraw(-n); } public void transfer(Account other, int n) { this.withdraw(n); other.deposit(n); } }

Slide 23

Slide 23 text

class Account { public void transfer(Account other, int n) { if (this.id < other.id) { this.lock.lock(); other.lock.lock(); } else { other.lock.lock(); this.lock.lock(); } try { this.amount -= n; other.amount += n; } finally { if (this.id < other.id) { this.lock.unlock(); other.lock.unlock(); } else { other.lock.unlock(); this.lock.unlock(); } } } }

Slide 24

Slide 24 text

import System.IO import Control.Concurrent.STM type Account = TVar Int withdraw :: Account -> Int -> STM () withdraw acc amount = do bal <- readTVar acc writeTVar acc (bal - amount) deposit :: Account -> Int -> STM () deposit acc amount = withdraw acc (- amount) transfer :: Account -> Account -> Int -> IO () transfer from to amount = atomically $ do deposit to amount withdraw from amount amount :: Account -> IO Int amount acc = atomically $ readTVar acc

Slide 25

Slide 25 text

main = do -- create accounts from <- atomically (newTVar 200) to <- atomically (newTVar 100) -- transfer amount transfer from to 50 -- get amounts and print v1 <- amount from v2 <- amount to putStrLn $ (show v1) ++ ", " ++ (show v2) -- prints "150, 150"

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

retry :: STM a checkedWithdraw :: Account -> Int -> STM () checkedWithdraw acc amount = do bal <- readTVar acc if amount >= bal then writeTVar acc (bal - amount) else retry orElse :: STM a -> STM a -> STM a backupWithdraw :: Account -> Account -> Int -> STM () backupWithdraw acc1 acc2 amount = checkedWithdraw acc1 amt `orElse` checkedWithdraw acc2 amt

Slide 28

Slide 28 text

data TChan a newTChan :: STM (TChan a) writeTChan :: TChan a -> a -> STM () readTChan :: TChan a -> STM a

Slide 29

Slide 29 text

Haskell STM » Pros » Composable atomicity and blocking. » Type system prevents side effects. » Dataflow programming with TChans. » Cons » Transactions which touch many variables are expensive. » A long-running transaction may re-execute indefinitely because it may be repeatedly aborted by shorter transactions.

Slide 30

Slide 30 text

Communicating Sequential Processes

Slide 31

Slide 31 text

Communicating Sequential Processes » Independent threads of activity. » Synchronous communication through channels. » Multiplexing of channels with alternation.

Slide 32

Slide 32 text

Source: arild.github.io/csp-presentation

Slide 33

Slide 33 text

(go (println "hi")) (def echo-chan (chan)) (go (println (! echo-chan "hello")) ; => hello (def echo-chan (chan 10))

Slide 34

Slide 34 text

(let [c1 (chan) c2 (chan) c3 (chan)] (dotimes [n 3] (go (let [[v ch] (alts! [c1 c2 c3])] (println "Read" v "from" ch)))) (go (>! c1 "hello")) (go (>! c2 "allo") (go (>! c2 "hola"))) ; => Read allo from # ; => Read hola from # ; => Read hello from #

Slide 35

Slide 35 text

(parse-to-state-machine (macroexpand-1 `(clojure.core.async/go (if (= :x 1) :true :false))) {})

Slide 36

Slide 36 text

[inst_10125 {:bindings {:terminators ()}, :block-id 1, :blocks {1 [{:ast (let [c__7560__auto__ (core.async/chan 1) captured-bindings__7561__auto__ (Var/getThreadBindingFrame)] (core.async.impl.dispatch/run (fn* [] (let [f__7562__auto__ (fn state-machine__7066__auto__ ([] (core.async.impl.ioc-macros/aset-all! (java.util.concurrent.atomic.AtomicReferenceArray. 7) 0 state-machine__7066__auto__ 1 1)) ([state_10123] (let [old-frame__7067__auto__ (Var/getThreadBindingFrame) ret-value__7068__auto__ (try (Var/resetThreadBindingFrame (core.async.impl.ioc-macros/aget-object state_10123 3)) (loop [] (let [result__7069__auto__ (case (int (core.async.impl.ioc-macros/aget-object state_10123 1)) 1 (let [inst_10121 (if (= :x 1) :true :false)] (core.async.impl.ioc-macros/return-chan state_10123 inst_10121)))] (if (identical? result__7069__auto__ :recur) (recur) result__7069__auto__))) (catch java.lang.Throwable ex__7070__auto__ (core.async.impl.ioc-macros/aset-all! state_10123 2 ex__7070__auto__) (if (seq (core.async.impl.ioc-macros/aget-object state_10123 4)) (core.async.impl.ioc-macros/aset-all! state_10123 1 (first (core.async.impl.ioc-macros/aget-object state_10123 4)) 4 (rest (core.async.impl.ioc-macros/aget-object state_10123 4))) (throw ex__7070__auto__)) :recur) (finally (Var/resetThreadBindingFrame old-frame__7067__auto__)))] (if (identical? ret-value__7068__auto__ :recur) (recur state_10123) ret-value__7068__auto__)))) state__7563__auto__ (-> (f__7562__auto__) (core.async.impl.ioc-macros/aset-all! core.async.impl.ioc-macros/USER-START-IDX c__7560__auto__ core.async.impl.ioc-macros/BINDINGS-IDX captured-bindings__7561__auto__))] (core.async.impl.ioc-macros/run-state-machine-wrapped state__7563__auto__)))) c__7560__auto__), :locals nil, :id inst_10124} {:value inst_10124, :id inst_10125}]}, :block-catches {1 nil}, :start-block 1, :current-block 1}]

Slide 37

Slide 37 text

Source: Implementation details of core.async Channels - Rich Hickey

Slide 38

Slide 38 text

Source: Implementation details of core.async Channels - Rich Hickey

Slide 39

Slide 39 text

Clojure core.async » Pros » Just a library. No special runtime needed. » Easy Dataflow programming with many combinators available. » Works on JVM and browser. » Cons » Doing blocking IO in go-threads blocks them. » Error handling is complicated.

Slide 40

Slide 40 text

Learnings » Green threads FTW. » Decomplect using higher level concurrency models. » Different techniques are suitable for different situations.

Slide 41

Slide 41 text

References » Software Transactional Memory chapter from the Parallel and Concurrent Programming in Haskell book » Processes chapter from The Erlang Runtime System book » Timothy Baldridge's screencasts on Clojure/ core.async internals » Rich Hickey's talk on Clojure/core.async internals

Slide 42

Slide 42 text

Thanks @abhin4v abhinavsarkar.net