Actor Concurrency

63
Actor Concurrency Alex Miller Blog: http://tech.puredanger.com Twitter: @puredanger

description

An introduction to Erlang and the actor model of concurrency. Author: Alex MillerBlog: http://tech.puredanger.comTwitter: @puredanger

Transcript of Actor Concurrency

Page 1: Actor Concurrency

Actor ConcurrencyAlex Miller

Blog: http://tech.puredanger.com Twitter: @puredanger

Page 2: Actor Concurrency
Page 3: Actor Concurrency
Page 4: Actor Concurrency

8086

Page 5: Actor Concurrency
Page 6: Actor Concurrency
Page 10: Actor Concurrency

State of the Practice

public class Counter { private int count = 0;

public int increment() { synchronized(this) { return ++count; } }}

Page 11: Actor Concurrency
Page 12: Actor Concurrency

• 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:

Page 13: Actor Concurrency

• 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:

Page 14: Actor Concurrency

The problem with shared state concurrency

Page 15: Actor Concurrency

The problem with shared state concurrency

...is the shared state.

Page 16: Actor Concurrency

Actors

• No shared state

• Lightweight processes

• Asynchronous message-passing

• Buffer messages in a mailbox

• Receive messages with pattern matching

Page 17: Actor Concurrency

The Basics

Page 18: Actor Concurrency

The Basics

spawn

Page 19: Actor Concurrency

The Basics

spawn

send

Page 20: Actor Concurrency

The Basics

receive

Page 21: Actor Concurrency

Erlang

• Invented at Ericsson

• Functional language

• Dynamic typing

• Designed for concurrency, fault-tolerance

Page 22: Actor Concurrency

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

Page 23: Actor Concurrency

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"

Page 24: Actor Concurrency

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

Page 25: Actor Concurrency

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]

Page 26: Actor Concurrency

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

Page 27: Actor Concurrency

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

Page 28: Actor Concurrency

Concurrency Primitives

• Pid = spawn(fun)

• Pid ! message

• receive...end

Page 29: Actor Concurrency

Concurrency Primitives

• Pid = spawn(fun)

• Pid ! message

• receive...end Pid

Page 30: Actor Concurrency

Concurrency Primitives

• Pid = spawn(fun)

• Pid ! message

• receive...end Pid

Page 31: Actor Concurrency

Concurrency Primitives

• Pid = spawn(fun)

• Pid ! message

• receive...end Pid

Page 32: Actor Concurrency

Concurrency Primitives

• Pid = spawn(fun)

• Pid ! message

• receive...end Pidreceive

...end

Page 33: Actor Concurrency

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.

Page 34: Actor Concurrency

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}

Page 35: Actor Concurrency

Responding-module(temp2).-export([start/0,convert/2]).

start() -> spawn(fun() -> loop() end).

convert(Pid, Request) -> Pid ! {self(), Request}, receive {Pid, Response} -> Response end.

Page 36: Actor Concurrency

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.

Page 37: Actor Concurrency

Responding1> c(temp2). {ok,temp2}2> Pid = temp2:start().<0.57.0>3> temp2:convert(Pid2, {toF, 100}).212.0

Page 38: Actor Concurrency

Process Ring

Page 39: Actor Concurrency

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

Page 40: Actor Concurrency

Processes and Messages

• Processes cheap to create (10ks < second)

• Create many processes (10ks to 100ks)

• Messages very fast to send (1M / second)

Page 41: Actor Concurrency

Error Handling

Page 42: Actor Concurrency

Error Handling

link

Page 43: Actor Concurrency

Error Handling

link

Page 44: Actor Concurrency

Error Handling

exit signal

Page 45: Actor Concurrency

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).

Page 46: Actor Concurrency

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}]}

Page 47: Actor Concurrency

Does it work?

Page 48: Actor Concurrency

Ericsson AXD301

• Telecom ATM switch

• Millions of calls / week

• 99.9999999% uptime = 32 ms down / year

• -> Nortel Alteon SSL Accelerator

Page 49: Actor Concurrency

YAWS

http://www.sics.se/~joe/apachevsyaws.html

Page 50: Actor Concurrency

Messaging

Page 51: Actor Concurrency

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

Page 52: Actor Concurrency

And others...

Page 53: Actor Concurrency

• JVM-based language

• Object-functional hybrid

• Actor library

Page 54: Actor Concurrency

Scala Ringsdef act() { loop { react { case StartMessage => { log("Starting messages") timer ! StartMessage nextNode ! TokenMessage(nodeId, 0) } case StopMessage => { log("Stopping") nextNode ! StopMessage exit }

Page 55: Actor Concurrency

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) } }

Page 56: Actor Concurrency

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)

Page 57: Actor Concurrency

• 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)

Page 58: Actor Concurrency

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;}

Page 59: Actor Concurrency

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;

...

Page 60: Actor Concurrency

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); } } }

Page 61: Actor Concurrency

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

Page 62: Actor Concurrency