Toy Factory Simulation Using core.async (1)
A core.async example
After learning the core.async
library in Clojure, I was craving for a real-world example. Now I found one from purelyfunctional.tv.
The original code can be found here.
The Toy Car Factory
Let’s describe how a worker builds a toy car.
- Take a car body from the part box.
- Take a wheel from the part box.
- Take another wheel from the part box.
- Attach the first wheel to the body.
- Attach the second wheel to the body.
- Box the car.
- Put it in truck.
Now let’s see some code that implement these steps.
take-part
will take a wheel or body randomly from the part box.
(defn try-to-take [state]
(case state
:free :ok
:ok :already-taken
:already-taken :already-taken))
(def part-box (atom :free))
(defn take-part []
"Take a wheel or body randomly. This is blocking."
(Thread/sleep 200)
(let [status (swap! part-box try-to-take)]
(if (not= :ok status)
(do
(Thread/sleep 100)
(recur))
(let [r (rand-nth [:wheel :wheel (atom {:body []
:status :free})])]
(Thread/sleep 800)
(reset! part-box :free)
r))))
The clever part of the code is the use of (swap! part-box try-to-take)
.
Let’s say worker-1 on process-1 is the first worker to enter this function. After the swap!
, the part-box
becomes :ok
. And when worker-1 is at (Thread/sleep 800)
, there is another worker-2 on process-2 also enters the function. But when worker-2 executes (swap! part-box try-to-take)
, it will return :already-taken
and be forced to retry. Only when worker-1 has done his work and (reset! part-box :free)
will worker-2 have a chance to get in. As you can see, this actually is a kind of locking mechanism, preventing multiple workers from taking the part simultaneously. This is quite reasonable, cause there is only one box.
attach-wheel
attaches a wheel to a car body.
(defn attach-wheel [body wheel]
(let [{status :status} (swap! body update-in [:status] try-to-take)]
(if (not= :ok status)
(do
(Thread/sleep 100)
(recur body wheel))
(do
(Thread/sleep 4000)
(swap! body #(-> %
(assoc :status :free)
(update-in [:body] conj wheel)))
body))))
A few things about this function:
- Attaching wheel to the body takes 4 secs.
- There cannot be two workers attaching wheels to the same body at the same time.
- Multiple workers can attaching wheels to different bodies on different processes at the same time.
box-up
puts the car into box.
(defn box-up [body]
(Thread/sleep 3000)
:box)
Boxing up a car takes 3 secs, and multiple workers can do it at the same time on different threads.
(def truck (atom :free))
(defn put-in-truck [box]
(let [status (swap! truck try-to-take)]
(if (not= :ok status)
(do
(Thread/sleep 100)
(recur box))
(do
(Thread/sleep 2000)
(reset! truck :free)
:done))))
Puting a box in the car takes 2 seconds, and only one worker can do it at a time, because there is only one truck of course.
Now we have all the pieces, lets analyse the whole process if there is one worker doing it all.
- Take a car body from the part box. 1 sec
- Take a wheel from the part box. 1 sec
- Take another wheel from the part box. 1 sec
- Attach the first wheel to the body. 4 sec
- Attach the second wheel to the body. 4 sec
- Box the car. 3 sec
- Put it in truck. 2 sec
TOTAL 16 sec
Let’s write some code to verify it.
(defn build-car [n]
(prn n "Starting build")
(let [body (loop []
(let [part (take-part)]
(if (body? part)
part
(recur))))
_ (prn n "Got body")
wheel1 (loop []
(let [part (take-part)]
(if (wheel? part)
part
(recur))))
_ (prn n "Got wheel1")
wheel2 (loop []
(let [part (take-part)]
(if (wheel? part)
part
(recur))))
_ (prn n "Got wheel2")
bw (attach-wheel body wheel1)
_ (prn n "Attach wheel1")
bww (attach-wheel bw wheel2)
_ (prn n "Attach wheel2")
box (box-up bww)
_ (prn n "Box up")]
(put-in-truck box)
(prn "Done!")))
Notice since the parts taken from the box is random, so there may be times when we want a wheel but get a body. So the total time can be more than 16 secs.
The result is indeed what think:
lispcast-clojure-core-async.core> (time (build-car 1))
1 "Starting build"
1 "Got body"
1 "Got wheel1"
1 "Got wheel2"
1 "Attach wheel1"
1 "Attach wheel2"
1 "Box up"
"Done!"
"Elapsed time: 18008.065267 msecs"
nil
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Email