Actor ConcurrencyAlex Miller
Blog: http://tech.puredanger.com Twitter: @puredanger
8086
Moore’s Law?
http://en.wikipedia.org/wiki/CPU_speed http://en.wikipedia.org/wiki/Transistor_count
Moore’s Law?
http://en.wikipedia.org/wiki/CPU_speed http://en.wikipedia.org/wiki/Transistor_count
Moore’s Law?
http://en.wikipedia.org/wiki/CPU_speed http://en.wikipedia.org/wiki/Transistor_count
State of the Practice
public class Counter { private int count = 0;
public int increment() { synchronized(this) { return ++count; } }}
• It’s the mutable state, stupid.
• Make fields final unless they need to be mutable.
• Immutable objects are automatically thread-safe.
• Encapsulation makes it practical to manage the complexity.
• Guard each mutable variable with a lock.
• Guard all variables in an invariant with the same lock.
• Hold locks for the duration of compound actions.
• A program that accesses a mutable variable from multiple threads without synchronization is a broken program.
• Don’t rely on clever reasoning about why you don’t need to synchronize.
• Include thread safety in the design process - or explicitly document that your class is not thread-safe.
• Document your synchronization policy.
JCIP says:
• It’s the mutable state, stupid.
• Make fields final unless they need to be mutable.
• Immutable objects are automatically thread-safe.
• Encapsulation makes it practical to manage the complexity.
• Guard each mutable variable with a lock.
• Guard all variables in an invariant with the same lock.
• Hold locks for the duration of compound actions.
• A program that accesses a mutable variable from multiple threads without synchronization is a broken program.
• Don’t rely on clever reasoning about why you don’t need to synchronize.
• Include thread safety in the design process - or explicitly document that your class is not thread-safe.
• Document your synchronization policy.
JCIP says:
The problem with shared state concurrency
The problem with shared state concurrency
...is the shared state.
Actors
• No shared state
• Lightweight processes
• Asynchronous message-passing
• Buffer messages in a mailbox
• Receive messages with pattern matching
The Basics
The Basics
spawn
The Basics
spawn
send
The Basics
receive
Erlang
• Invented at Ericsson
• Functional language
• Dynamic typing
• Designed for concurrency, fault-tolerance
Single assignmentEshell V5.6.3 (abort with ^G)1> A = 4.42> A.43> A = 6.** exception error: no match of right hand side value 64> A = 2*2.4
Atoms and Tuples1> F=foo.foo2> Stooges = {larry,curly,moe}.{larry,curly,moe}3> Me = {person,"Alex","nachos"}.{person,"Alex","nachos"}4> {person,Name,Food} = Me.{person,"Alex","nachos"}5> Name."Alex"6> Food."nachos"
Lists1> List=[1,2,3].[1,2,3]2> [First|Rest]=List.[1,2,3]3> First.14> Rest.[2,3]5> List2=[4|List].[4,1,2,3]6> [Char|String] = “abc”.“abc”7> Char.97
Functions1> Square = fun(X) -> X*X end.#Fun<erl_eval.6.13229925>2> Square(5).25.
3> TimesN = fun(N) -> (fun(X) -> N*X end)3> end.#Fun<erl_eval.6.13229925>4> lists:map(TimesN(10), [1,2,3]).[10,20,30]
Modules-module(mymath).-export([square/1,fib/1]).
square(Value) -> Value*Value.
fib(0) -> 0;fib(1) -> 1;fib(N) when N>0 -> fib(N-1) + fib(N-2).
1> c(mymath.erl).2> mymath:square(5).253> mymath:fib(7).13
Modules and Functions-module(mymath2).-export([fibtr/1]).
fibtr(N) -> fibtr_iter(N, 0, 1).
fibtr_iter(0, Result, _Next) -> Result;fibtr_iter(Iter, Result, Next) when Iter > 0 ->fibtr_iter(Iter-1, Next, Result+Next).
1> c(mymath2.erl).2> mymath2:fibtr(200).280571172992510140037611932413038677189525
Concurrency Primitives
• Pid = spawn(fun)
• Pid ! message
• receive...end
Concurrency Primitives
• Pid = spawn(fun)
• Pid ! message
• receive...end Pid
Concurrency Primitives
• Pid = spawn(fun)
• Pid ! message
• receive...end Pid
Concurrency Primitives
• Pid = spawn(fun)
• Pid ! message
• receive...end Pid
Concurrency Primitives
• Pid = spawn(fun)
• Pid ! message
• receive...end Pidreceive
...end
A Simple Processloop() -> receive {toF, C} -> io:format("~p C is ~p F~n", [C, 32+C*9/5]), loop(); {toC, F} -> io:format("~p F is ~p C~n", [F, (F-32)*5/9]), loop(); {stop} -> io:format("Stopping~n"); Other -> io:format("Unknown: ~p~n", [Other]), loop() end.
Spawn!1> c(temperature).{ok,temperature}2> Pid = spawn(fun temperature:loop/0).<0.128.0>3> Pid ! {toC, 32}.32F is 0.0 C{convertToC,32}4> Pid ! {toF, 100}.100C is 212.0 F{convertToF,100}5> Pid ! {stop}.Stopping{stop}
Responding-module(temp2).-export([start/0,convert/2]).
start() -> spawn(fun() -> loop() end).
convert(Pid, Request) -> Pid ! {self(), Request}, receive {Pid, Response} -> Response end.
Respondingloop() -> receive {From, {toF, C}} -> From ! {self(), 32+C*9/5}, loop(); {From, {toC, F}} -> From ! {self(), (F-32)*5/9}, loop(); {From, stop} -> From ! {self(), stop}, io:format("Stopping~n"); {From, Other} -> From ! {self(), {error, Other}}, loop() end.
Responding1> c(temp2). {ok,temp2}2> Pid = temp2:start().<0.57.0>3> temp2:convert(Pid2, {toF, 100}).212.0
Process Ring
Running Rings1> c(ring).2> T=ring:startTimer().3> R=ring:startRing(100,T).spawned 100 in 241 microseconds4> R ! start.{1230,863064,116530} Starting message{1230,863064,879862} Around ring 10000 times {1230,863065,642097} Around ring 20000 times ...etc {1230,863140,707023} Around ring 990000 times {1230,863141,471193} Around ring 1000000 times Start={1230,863064,116875} Stop={1230,863141,471380} Elapsed=773545055> R20k = ring:startRing(20000,T). spawned: 20000 in 118580 microseconds
Processes and Messages
• Processes cheap to create (10ks < second)
• Create many processes (10ks to 100ks)
• Messages very fast to send (1M / second)
Error Handling
Error Handling
link
Error Handling
link
Error Handling
exit signal
Linked ProcessesreportExit(WatchedPid) -> spawn(fun() -> process_flag(trap_exit, true), link(WatchedPid), receive {'EXIT', _Pid, Msg} -> io:format("got linked exit: ~p~n", [Msg]) end end).
startWatched() -> spawn(fun() -> receive die -> exit("die"); crash -> erlang:error("crash") end end).
Linked Processes1> c(link).2> P1 = link:startWatched().3> P2 = link:startWatched().4> link:reportExit(P1).5> link:reportExit(P2).6> P1 ! die.got linked exit: "die"7> P2 ! crash.got linked exit: {"crash",[{link,'-startWatched/0-fun-0-',0}]}
Does it work?
Ericsson AXD301
• Telecom ATM switch
• Millions of calls / week
• 99.9999999% uptime = 32 ms down / year
• -> Nortel Alteon SSL Accelerator
YAWS
http://www.sics.se/~joe/apachevsyaws.html
Messaging
Facebook Chat
“For Facebook Chat, we rolled our own subsystem for logging chat messages (in C++) as well as an epoll-driven web server (in Erlang) that holds online users' conversations in-memory and serves the long-polled HTTP requests. Both subsystems are clustered and partitioned for reliability and efficient failover. ”
Reference: http://www.facebook.com/note.php?note_id=14218138919
And others...
• JVM-based language
• Object-functional hybrid
• Actor library
Scala Ringsdef act() { loop { react { case StartMessage => { log("Starting messages") timer ! StartMessage nextNode ! TokenMessage(nodeId, 0) } case StopMessage => { log("Stopping") nextNode ! StopMessage exit }
Scala Rings case TokenMessage(id,value) if id == nodeId => { val nextValue = value+1 if(nextValue % 10000 == 0) log("Around ring " + nextValue + " times")
if(nextValue == 1000000) { timer ! StopMessage timer ! CancelMessage nextNode ! StopMessage exit } else { nextNode ! TokenMessage(id, nextValue) } } case TokenMessage(id,value) => { nextNode ! TokenMessage(id,value) } }
Comparison to Erlang
• receive-based actors cheap to create (Erlang about 3x faster)
• Can also create many processes
• Messages also fast to send (Erlang about 2x faster)
• Java-based framework with compile-time weaving
• Tasks - lightweight threads/processes/etc
• @pausable - let task be paused during execution
• sleep / yield
• Mailbox (typed via generics) - single consumer
• Messaging - mutable (in limited ways)
Kilim Ringimport kilim.Mailbox;import kilim.Task;import kilim.pausable;
public class NodeActor extends Task { private final int nodeId; private final Mailbox<Message> inbox; private final Mailbox<Message> timerInbox; private Mailbox<Message> nextInbox;
public NodeActor(int nodeId, Mailbox<Message> inbox, Mailbox<Message> timerInbox) { this.nodeId = nodeId; this.inbox = inbox; this.timerInbox = timerInbox;}
Kilim Ring
@pausablepublic void execute() throws Exception { while(true) { Message message = inbox.get(); if(message.equals(Message.START)) { timerInbox.putnb(Message.START); nextInbox.putnb(new TokenMessage(nodeId, 0)); } else if(message.equals(Message.STOP)) { nextInbox.putnb(Message.STOP); break;
...
Kilim Ring } else if(message instanceof TokenMessage) { TokenMessage token = (TokenMessage)message; if(token.source == nodeId) { int nextVal = token.value+1; if(nextVal == 1000000) { timerInbox.putnb(Message.STOP); timerInbox.putnb(Message.CANCEL); nextInbox.putnb(Message.STOP); break; } else { token.value = nextVal; nextInbox.putnb(token); } } else { nextInbox.putnb(token); } } }
Performance
0
2.5
5.0
7.5
10.0
100 nodes
mill
isec
onds
ErlangScalaKilim
0
32,500
65,000
97,500
130,000
100M messages
mill
isec
onds
0
100
200
300
400
20k nodes
mill
isec
onds
http://tech.puredanger.com/presentations/actor-concurrency