Vous êtes sur la page 1sur 32

ASYNCHRONOUS CONCURRENCY

IN CLOJURE
and (a few) other languages

A Quest

v 1.0

Michael Harrison
Capital 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 is not

a technology

a pattern

a methodology

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

The spectrum of Java thread-use danger

Soft, foam-covered “You’ll shoot your


corners eye out!”

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
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.
(send myagent inc)
(send-off myagent #(* 2 %))

That creation and assignment occurs asynchronously. It


won't be coordinated with your other code.
@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
Asynchronous computations occurring in other threads.
(def myfuture
(future (* 2 2)))

Dereferencing a future will block if the expression has


not finished.

All subsequent dereferencing will return the value.


@future ; => 4
@future ; => 4

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
may be evaluated only once
transformed

immediately returns a value blocks a thread

16
Examples: Agents
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).

Stuart Sierra’s “Agents of Swing” blog post is a great


example. (Thanks, Sean Devlin)

17
Examples: Agents
Stuart Sierra’s “Agents of Swing” blog post
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
Examples: Futures

Practically: Futures are good for decomposing larger


computations.
pmap (as of Clojure 1.2) is implemented with
futures.

19
Examples: Futures
pmap (as of Clojure 1.2)

(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
Advantage: Clojure

For multithreaded programming, Clojure is powerful


and much less painful than Java.

The spectrum of Java thread-use danger

Soft, foam-covered “You’ll shoot your


corners eye out!”
Clojure will let you
do dangerous stuff
if you really want
Idiomatic Clojure’s exposure to shoot your eye
out.

21
Another Way?

Fact: Threads are fairly heavyweight.


Fact: Threads still give some people the howling fantods.
Fact: A lot of program latency is blocking IO.

22
Another Way?
What if I could interleave executions, without
threading, so one could work while another blocked?

Compute Blocking operation Compute Blocking operation Compute

Compute Blocking operation Compute

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 the meantime, the service handler executes other


code in a similar fashion, until it blocks or completes.

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.

24
Evented IO in JavaScript: Node.js

JavaScript web programmers are used to specifying


callbacks for DOM events or Ajax actions. Node
extends this capacity to IO events.

http://nodejs.org/

25
Evented IO in JavaScript: Node.js
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");
26
Distributed Computing with Tuple Spaces
A server maintains a “blackboard” of tuples, arrays of
mixed-type values.

A client may write a tuple to the blackboard, suggesting


a computation that needs to be done.

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

A one-sided conversation:

emitter process
while true
tuple_space.write [:message, gets]
end

receiver process
while true
puts tuple_space.take([:message, nil])[1]
end

http://jpz-log.info/archives/2007/10/11/ruby-fun-with-rinda/
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

Non-threaded methods may be better for networked


applications

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.
31
Shameless pitch
I’m looking for a gig.

mh@michaelharrison.ws
@goodmike

If you’re interested in
what I know and like
the way I think, do get
in touch.
:->

32

Vous aimerez peut-être aussi