(defn evens [] (iterate (partial + 2) 0)) (defn odds [] (iterate (partial + 2) 1))
(def foo (agent [])) (defn push-foo [s] (doseq [x s] (Thread/sleep (rand-int 100)) (send foo conj x))) (defn pusher-threads [ss] (map (fn [s] (Thread. #(do (push-foo s) (println "done!")))) ss)) (doseq [t (pusher-threads [(take 10 (evens)) (take 10 (odds))])] (.start t)) foo Typical output: user=> (doseq [t (pusher-threads [(take 10 (evens)) (take 10 (odds))])] (.start t)) nil done! done! user=> foo #<Agent [0 1 2 4 3 6 5 8 7 10 12 9 11 14 16 13 15 18 17 19]> user=> (filter even? @foo) (0 2 4 6 8 10 12 14 16 18) user=> (filter odd? @foo) (1 3 5 7 9 11 13 15 17 19) The odds are interleaved more or less randomly with the evens, but the odds are themselves in sequence, as are the evens, and nothing is missing. This demonstrates the documented behavior of agents: no race conditions and the sends from any single thread are processed in the order they were sent. This might be useful for someone just learning clojure. (Other things demonstrated: creation of simple infinite integer sequences with iterate; avoiding holding onto the heads of same by not def'ing them directly but instead defining generator functions; doseq for running a side effect for each element of a collection, x2; Java interop with the Thread class: static method invocation, constructor call with argument, and instance method invocation; rand-int; println for monitoring execution of code; do for bundling multiple expressions in an if clause or #(...); accumulating stuff in a vector with conj; closures, both (fn ...) and #(...), and partial to generate functions in-line; even? and odd?; def and defn; and map, take, and filter.) A detailed breakdown of the code that may also be useful for beginners: The evens function returns (iterate (partial + 2) 0), which returns a sequence starting with 0 and continuing by adding 2 to each previous item -- mathematically, a recurrence. Since it's just adding a fixed amount each step, it's a special subtype called an arithmetic progression. In this case it's just the nonnegative even numbers in ascending order. The odds function does almost the same thing, but the starting point of its sequence is 1 and so every number is one higher than in evens. This obviously yields up the nonnegative odd numbers. Directly defining vars holding these sequences would hold onto their heads; generally a bad idea with large or infinite sequences. Instead it's better to define a no-arg function to produce fresh instances as needed, so you can grab a copy, work with it, and lose its head. Hence making evens and odds functions rather than vars directly holding the sequences. The foo agent initially holds an empty vector. The push-foo function takes a sequence of objects and pushes them onto the agent's vector using (send foo conj x). The vector thus accumulates the values. It waits a random interval before each push, however, up to 100ms, using the Thread/sleep Java method. This ensures that the threads don't run so fast that one finishes in the time it takes for the other to be launched (the start method seems to be quite slow, and, the way this code is written, the .start calls use reflection), or interleave too predictably (say, [0 1 2 3 ...]). The pusher-threads function takes a sequence of sequences and for each sequence s creates a Thread whose body is (do (push-foo s) (println "done!")). # #(do (push-foo s) (println "done!")) encapsulates that as a closure, (Thread. # #(do (push-foo s) (println "done!"))) creates a Thread object with that closure as the code it will run when started, and the (fn [s] ...) form wraps this up in a larger closure, which map runs for each sequence in the input to produce a sequence of threads, each of which will, when started, run push-foo on one of the input sequences and then announce that it is done. The doseq line that does the deed is trickier as there's lots going on in it. (take 10 (evens)) calls (evens) to get an instance of the even-numbers sequence and chops off the tail to make it finite. (take 10 (odds)) does likewise with the odd numbers. The two are enclosed in square brackets [...] -- an expression that will evaluate to a two-element vector, one with the first ten nonnegative even numbers followed by the first ten nonnegative odd numbers. Calling pusher-threads on this vector produces a sequence of two pusher threads, one to push the evens and one to push the odds. The doseq itself runs over these threads starting each one with (.start t), which invokes the Java instance method start() on the Thread t, launching it. The doseq returns nil but the threads chug along autonomously and concurrently, waiting random intervals and sending conjes to the agent foo holding the initially empty vector. Each prints a "done!" line when it is finished with its sequence of numbers to send to the agent to conj onto the vector. After roughly one second, both should be done on a dual-core-or-more machine; it could take up to two seconds on a single-core machine. (Most of the time in both threads is spent in Thread/sleep, which doesn't cause contention, and some more is spent generating sequence elements, which also doesn't; very little is spent in the agent thread pool conjing onto the vector, by contrast.) At that point, printing the value of foo should produce a sequence of the numbers from zero to 19 without any missing, somewhat out of order but roughly ascending. If two agent operations on the same agent ran concurrently, one might read the vector, generate a new vector by conjing say 7 onto the end, and then set the agent's state to the new vector while another read the state, conjed 10 onto the end, and set the agent's state to THAT vector; if they both read the state before either the 7 or the 10 got added, and got [0 1 2 4 3 6 5 8], then one would set the agent's state to that with either the 7 or the 10 added, and then the other would set it to that with the other added instead, and you'd have either [0 1 2 4 3 6 5 8 7] or [0 1 2 4 3 6 5 8 10] afterward. In one case the 10 gets lost and in the other the 7 gets lost. This is called a "race condition" and it's bad. You probably don't want your computer randomly losing information it was told to store somewhere! But agents avoid race conditions by executing their sends one at a time, in some order. Both 2 and 4 got added between 1 getting added and 3 getting added, in this instance, but in general, the vector is read, something is conjed on, and the state is set to the new vector; then the vector is read, something is conjed on, and the state is set; and so on, without any overlaps that would cause any of the numbers to get lost. The expression (filter even? @foo) extracts the value held by foo, then extracts just the even numbers, in the order they occur in foo's vector, presenting them as a sequence; (filter odd? @foo) does the same thing except it extracts just the odd numbers. The two sequences, though interleaved in foo's vector, are each separately in numerical order, because agent sends from a single thread are processed in the order they were sent. Since (take 10 (odds)) produced 1, 3, 5, ..., 19 in order, and (push-foo ...) sent corresponding conj messages to foo in order and from a single thread, the odd numbers got conjed on in order, though sometimes one or more even numbers got conjed on in between two successive odd numbers. The whole thing can be reset by using (send foo (constantly [])) to wipe the vector clean. (constantly x) returns a function that always returns x, so sending (constantly x) to an agent will cause the agent to replace its state with x after a while. Another three runs produced the following successive outputs on my machine: [0 1 3 2 5 7 4 9 6 8 11 10 13 12 15 14 16 18 17 19] [1 0 3 2 4 5 7 9 6 8 11 10 13 12 14 15 17 19 16 18] [0 1 2 3 5 4 7 6 8 10 9 12 14 11 16 13 18 15 17 19] -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en