asynchronous concurrency in clojure and a few other languages
DESCRIPTION
A presentation for the Capital Area Clojure Group May 27, 2010:Concurrent programming n. a quest to achieve some perennial goals, by going beyond serial execution on a single processor.TRANSCRIPT
ASYNCHRONOUS CONCURRENCY IN CLOJURE
and (a few) other languages
A Quest
v 1.0
Michael HarrisonCapital Area Clojure Group
May 27, 2010
1
Fun Fact: “Concurrent programming” means different things to
different people
“Multi-processor”
“Distributed”
“Parallel”
“Scalable”
2
Concurrent programming n. a quest to achieve some perennial goals, by going beyond serial
execution on a single processor.
“I wanna go fast!”
“I need more power”
“I wish I could get more done.”
4
There are many approaches to concurrency
• Shared-memory, coordinated threads
• Shared-nothing event-based processes
• Parallel decomposition (e.g. Map/Reduce)
• Pure functions and immutable data
• Delayed task execution
• Collaborative multitasking
• Blackboard-style message-passing
5
What is asynchronous concurrency?
It isn’t transactional (except...)
It isn’t linear in time
Code executions happening conceptually in parallel, with minimal coordination with each other
6
How’s it work in Clojure?
Clojure is built on the JVM. It carefully uses Java’s thread pools and related
concurrency constructs.
Soft, foam-covered corners
“You’ll shoot your eye out!”
The spectrum of Java thread-use danger
7
How’s it work in Clojure?
Clojure is built on the JVM. It carefully uses Java’s thread pools and related
concurrency constructs.
This book is like the secret history of Clojure
8
Clojure’s main players: agent and future
(def myagent (agent 0))(send myagent inc)(send myagent #(* 2 %))@myagent
(def myfuture (future (* 2 2)))@future
(def impure-future(future (my-side-effect-fn) (* 2 2)))
9
Agent
That creation and assignment occurs asynchronously. It won't be coordinated with your other code.
(send myagent inc)(send-off myagent #(* 2 %))
Agents are an identity with a value (state).(def myagent (agent 0))
You can create a new value from the old one, and assign it to the identity, by sending the agent a
transforming function.
@myagent ;=> ?
10
clojure.lang.Agent
The Agent class maintains two static Java Executor services, one for use with send, the other for send-off.
final public static ExecutorService pooledExecutor = Executors.newFixedThreadPool(2 + Runtime.getRuntime ().availableProcessors());
final public static ExecutorService soloExecutor = Executors.newCachedThreadPool();
Clojure’s Java implementation may change, of course, but it’s worth knowing the details.
11
clojure.lang.Agent
When send or send-off is applied to an agent and a transforming function, an inner class Action is used.
static class Action implements Runnable{
final Agent agent; final IFn fn; final ISeq args; final boolean solo;
void execute(){ try { if(solo) soloExecutor.execute(this); else pooledExecutor.execute(this); } ...
12
clojure.lang.Agent
The action’s run method is invoked by the Executor. It in turns calls a static doRun method:
static void doRun(Action action){ try {
... try { Object oldval = action.agent.state; Object newval = action.fn.applyTo( RT.cons(action.agent.state, action.args)); action.agent.setState(newval); action.agent.notifyWatches(oldval,newval); ...
13
Future
All subsequent dereferencing will return the value.
@future ; => 4@future ; => 4
(def myfuture (future (* 2 2)))
Asynchronous computations occurring in other threads.
Dereferencing a future will block if the expression has not finished.
more on future basics at Sean Devlin's Full Disclojure screencast channel:http://vimeo.com/channels/fulldisclojure
14
clojure.core/future-call
(future [body]) is a macro that wraps body as a function of zero args to clojure.core/future-call
(defn future-call ... [^Callable f] (let [fut (.submit clojure.lang.Agent/soloExecutor f)] (reify clojure.lang.IDeref (deref [_] (.get fut)) java.util.concurrent.Future (get [_] (.get fut)) (get [_ timeout unit] (.get fut timeout unit)) (isCancelled [_] (.isCancelled fut)) (isDone [_] (.isDone fut)) (cancel [_ interrupt?] (.cancel fut interrupt?)))))
15
Agent vs. Future
Agent Future
encapsulates state encapsulates a function call
may be repeatedly transformed may be evaluated only once
immediately returns a value blocks a thread
16
Because agents encapsulate state and may be repeatedly transformed, they are good for running
(repeated) computations that some other process is going to check on later (repeatedly).
Examples: Agents
Stuart Sierra’s “Agents of Swing” blog post is a great example. (Thanks, Sean Devlin)
17
Stuart Sierra’s “Agents of Swing” blog post
Examples: Agents
http://stuartsierra.com/2010/01/08/agents-of-swing
(defn new-flipper [] (agent {:total 0, :heads 0, :running false, :random (java.util.Random.)}))
(defn calculate [state] (if (:running state) (do (send *agent* calculate) (assoc state :total (inc (:total state)) :heads (if (.nextBoolean (:random state)) (inc (:heads state)) (:heads state)))) state))
18
Practically: Futures are good for decomposing larger computations.
pmap (as of Clojure 1.2) is implemented with futures.
Examples: Futures
19
pmap (as of Clojure 1.2)
Examples: Futures
(defn pmap... ([f coll] (let [n (+ 2 (.. Runtime getRuntime availableProcessors)) rets (map #(future (f %)) coll) step (fn step [[x & xs :as vs] fs] (lazy-seq (if-let [s (seq fs)] (cons (deref x) (step xs (rest s))) (map deref vs))))] (step rets (drop n rets))))
20
Soft, foam-covered corners
“You’ll shoot your eye out!”
The spectrum of Java thread-use danger
Idiomatic Clojure’s exposure
Advantage: Clojure
For multithreaded programming, Clojure is powerful and much less painful than Java.
Clojure will let you do dangerous stuff if you really want to shoot your eye out.
21
Another Way?
Fact: Threads are fairly heavyweight.
Fact: A lot of program latency is blocking IO.
Fact: Threads still give some people the howling fantods.
22
Another Way?
What if I could interleave executions, without threading, so one could work while another blocked?
Compute
Compute
Compute Compute
Compute
Blocking operation Blocking operation
Blocking operation
23
Evented IO Concurrency
Blocking actions are given to a service handler. When the OS or network responds, the service handler
executes callback code.
In case you were curious, the service handler typically communicates with the OS via select, epoll, kqueue, dev/poll, etc. depending on the OS you’re running.
In the meantime, the service handler executes other code in a similar fashion, until it blocks or completes.
24
Evented IO in JavaScript: Node.js
http://nodejs.org/
JavaScript web programmers are used to specifying callbacks for DOM events or Ajax actions. Node
extends this capacity to IO events.
25
var tcp = require('tcp');var server = tcp.createServer(function (socket) { socket.setEncoding("utf8"); socket.addListener("connect", function () { socket.write("hello\r\n"); }); socket.addListener("data", function (data) { socket.write(data); }); socket.addListener("end", function () { socket.write("goodbye\r\n"); socket.end(); });});server.listen(7000, "localhost");
Evented IO in JavaScript: Node.js
26
Distributed Computing with Tuple Spaces
A client may write a tuple to the blackboard, suggesting a computation that needs to be done.
A server maintains a “blackboard” of tuples, arrays of mixed-type values.
Other clients may take a tuple that matches some criteria in order to perform the computation.
The computation’s result may then be written on the blackboard, within another tuple, for the original client
to take.
27
Tuple Spaces in Ruby: Rinda
while true tuple_space.write [:message, gets]end
while true puts tuple_space.take([:message, nil])[1]end
A one-sided conversation:
http://jpz-log.info/archives/2007/10/11/ruby-fun-with-rinda/
emitter process
receiver process
28
Oodles more
Don’t forget Erlang.
CouchDB’s replication and eventual consistency strategies put data and code “downstream” on your
machine.
“Ground-based computing” Some of CouchDB’s adherents use this term in contrast to cloud-based computing.
29
Sir Not-Appearing-in-This-Film
Measuring and improving performance
The power of reason and readability
30
Conclusions?
We aren’t there yet.
Frameworks, DSLs, and new langauges:Abstractions FTW
There’s probably a way for you to do it today
In general, good concurrent programming has a lot in common with plain old good programming.
Non-threaded methods may be better for networked applications
31
Shameless pitch
I’m looking for a gig.
[email protected]@goodmike
If you’re interested in what I know and like the way I think, do get in touch. :->
32