I. Message-Passing Concurrency A. motivation ------------------------------------------ MESSAGE-PASSING CONCURRENCY def: message passing is a programming style in which a program consists of that interact by Importance: ------------------------------------------ What is asynchronous? What's the importance of message passing? B. new concepts and capabilities 1. spawn ------------------------------------------ FEATURE: SPAWNING A PROCESS spawn function: creates a new process Pid = spawn(Fun) Pid2 = spawn(mymodule,myfun,[Arg1,Arg2,...]) Pid3 = spawn(OtherMachine,Fun) % on OtherMachine Pid4 = spawn(OtherMachine,mymodule,myfun,[Arg1,Arg2,...]) Example: main() -> _Other = spawn(fun rest/0). rest() -> timer:sleep(200), io:format("message from process ~w~n", [self()]). ------------------------------------------ 2. send and receive ------------------------------------------ FEATURE: SENDING MESSAGES ! expression: sends to a named process Pid ! Message - sends Message to named Pid, and continues - all clients can always send - value is the value of Message ------------------------------------------ ------------------------------------------ FEATURE: RECEIVING MESSAGES receive expression: receives sent messages Example: loop() -> receive {P, doIt, X} -> P ! {self(), answer, X+2}, loop(); {P, halt} -> P ! ok end Syntax: ::= ... receive end ::= | ; ::= -> ::= | when is data with possible variable declarations ::= | after -> Semantics: - each process has a mailbox - server can read all messages - all patterns processed for each message, in order of oldest message to newest Fundamentally nondeterministic - receive resolves races in message sends ------------------------------------------ What does it mean to be "named"? Do we still have referential transparency? How, would you look through all messages of one type first? C. semantics by example ------------------------------------------ SEND EXAMPLES (RACE CONDITIONS) -module(sendexamples). -export([main/0,server/0,client/2]). main() -> S = spawn(sendexamples,server,[]), spawn(sendexamples,client,[S,1]), spawn(sendexamples,client,[S,2]), spawn(sendexamples,client,[S,3]), spawn(sendexamples,client,[S,4]). server() -> receive({msg,N}) -> io:format("received ~b~n",[N]), server() end. dofor(I,F) -> if I > 0 -> F(), dofor(I-1,F); true -> done end. client(S,N) -> dofor(5, fun() -> W = rand:uniform(5), timer:sleep(W), S!{msg,N}, done end). ------------------------------------------ How would you do this in Java? II. actors ------------------------------------------ ACTORS def: An *actor* is a process that communicates with other processes by sending messages. i.e., an Erlang process Used as clients, servers, nodes in distributed systems Two types: - stateless (state doesn't change) - stateful ------------------------------------------ Why would you want to write a server without any state? A. stateless servers 1. math server ------------------------------------------ COMPUTATION SERVER EXAMPLE Math server, sends a message of the form {ok,Val} to Pid, where Val = math:f(math:g(... (math:h(X)) ...)) when it receives messages of the form: {Pid, compute, [f, g, ..., h], X} -module(mathserver). -export([start/0,compute/3]). start() -> mserver() -> accumulate ------------------------------------------ ------------------------------------------ CLIENT CODE % (written in the mathserver module) compute(P,Funs,Val) -> P!{self(),compute,Funs,Val}, receive {ok,Res} -> Res end. With this can do: P = mathserver:start(). mathserver:compute(P, [sin,cos], 1.0). ------------------------------------------ 2. exercise ------------------------------------------ FOR YOU TO DO Write a stateless server to compute factorials (of integers). ------------------------------------------ B. stateful servers 1. print spooler ------------------------------------------ PRINT SPOOLER EXAMPLE Spooler process handles messages of forms: {Client, print, Str} from a Client process, to print string Str {Printer, grab} from Printer process, to get a job to print {Client, status} from a Client process to return status of the spooler. ------------------------------------------ ------------------------------------------ PRINT SPOOLER CODE start() -> init() -> loop({queue, Jobs, Printers, Next}) -> ------------------------------------------ ------------------------------------------ PRINTER Sends {Me, grab} messages to spooler, then prints them start(Spooler) -> init(Spooler) -> loop({Me, Spooler}) -> grab(Spooler, Me) -> ------------------------------------------ ------------------------------------------ CLIENTS Asking to print by sending {Pid, print, Str} messages to spooler. Asking for status by sending {Pid, status} messages to spooler print(Spooler, Str) -> status(Spooler) -> ------------------------------------------ 2. resource arbiter ------------------------------------------ RESOURCE ARBITER Server that responds to 3 kinds of messages {Pid, status} sends Pid message: free or inUse {Pid, reserve} eventually sends Pid reserved, when no other process is using resource {Pid, release} lets the resource be used by another process ------------------------------------------ ------------------------------------------ WHAT STATE? What information does the resourcearbiter need? ------------------------------------------ ------------------------------------------ RESOURCEARBITER MODULE -module(resourcearbiter). -export([start/0, init/0]). -export_type([status/0]). start() -> spawn(?MODULE, init, []). init() -> loop( ------------------------------------------ 3. other examples ------------------------------------------ BUFFER start() returns pid of the server Receives messages of the form: {Pid, add, Val} remembers Val (at end of buffer) and sends {SPid, added} to Pid, where SPid is the server's pid {Pid, fetch} responds {SPid, value_is, V} to Pid where V is the oldest value in buffer and SPid is the server's pid, and no longer remembers V in buffer Remote Procedures based on these messages: add(BuffId, Val) -> BuffId ! {self(), add, Val}, receive {BuffId, added} -> ok end. fetch(BuffId) -> BuffId ! {self(), fetch}, receive {BuffId, value_is, V} -> V end. ------------------------------------------ III. generalizing servers ------------------------------------------ WHAT WOULD A GOOD FUNCTIONAL PROGRAMMER DO? We've seen several coding patterns in Erlang. So... ------------------------------------------ A. RPC pattern ------------------------------------------ RPC PATTERN Typical in Erlang code to make an RPC: RA ! {self(), reserve}, receive reserved -> ok end Can we make an abstraction of this? rpc(Name, Request) -> ------------------------------------------ Can we do the same for entire servers? B. server abstraction ------------------------------------------ SERVER ABSTRACTION (VERSION 1) Typical server: -module(server1). -export([start/2, rpc/2]). start(Name, Mod) -> register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)). % ...rpc as above... loop(Name, Mod, State) -> receive {From, Request} -> {Response, NewState} = Mod:handle(Request, State), From ! {Name, Response}, loop(Name, Mod, NewState) end. ------------------------------------------ What does this do? What is the initial state of the loop? What does the loop do? ------------------------------------------ GAUGE SERVER EXAMPLE Tracks a counter (an integer), responds to messages: count replies 'counted', and increments counter value replies {value_is, Counter} where Counter is the counter's value {changeTo, New} replies ok, and changes counter to New ------------------------------------------ How do we write this using server1? ------------------------------------------ -module(gauge_server). -export([init/0, count/0, value/0, changeTo/1, handle/2]). -import(server1, [rpc/2]). %% Calls that clients can make on this server count() -> value() -> changeTo(NewValue) -> %% callback routines init() ------------------------------------------ Do you see how that works? ------------------------------------------ FOR YOU TO DO Using server1, write a module shoppinglist that tracks a list of atoms, and responds to the messages: {add, Item} responds 'added' and remembers Item in the list getList responds with {list_is, Lst}, where Lst is the remembered list (in some order) clear responds 'cleared' and forgets all items in the list ------------------------------------------ What separation of concerns does this coding style allow? What other non-functional concerns might such a higher-order server handle? C. using the real gen_server ------------------------------------------ 3 SIMPLE STEPS FOR USING GEN_SERVER 1. Decide on callback module name. 2. Write the client interface functions 3. Write the callback functions in the callback module ------------------------------------------ 1. example, running average of last 3 ------------------------------------------ RUNNING AVERAGE OF 3 MEASUREMENTS SERVER module name: runavg interface functions: note(Measurement) records Measurement, returns 'ok' average() returns the current average if at least 3 measurements have been given Erlang code using genserver: ------------------------------------------