octopusgrabbus
octopusgrabbus

Reputation: 10685

How to launch two threads and wait for them

I can launch two threads and they work, but synchronously. What am I missing to get these threads independently launched?

main, thread, and output

(defn -main 
    [& args]
    (do
        (let [grid-dim-in [0 5]
              mr1-pos     [\N 2 4]
              mr2-pos     [\N 1 5]
              mr1-movs    "LMLMMRMM"
              mr2-movs    "RMRMMMLM"]

            (reset! grid-dim grid-dim-in)
            (reset! mr1-id {:mr1 mr1-pos})
            (reset! mr2-id {:mr2 mr2-pos})

            (.start (Thread. (rover-thread mr1-id mr1-movs update-work-block)))
            (.start (Thread. (rover-thread mr2-id mr2-movs update-work-block))))))

(defn rover-thread [id movs update-ref]
    (let [id-key (keys @id)
          id-vals (vals @id)]
        (doseq [mov movs]
           (println "Rover " id-key " is moving ")
           (let [new-mov (determine-rover-move (first id-vals) mov)]
               (move-rover id new-mov update-ref)
               (print "Rover ")
               (print (first id-key))
               (print " is at ")
               (println new-mov)
               (Thread/sleep (rand 1000)))))

Rover :mr1 is at [E 2 4]
Rover  (:mr1)  is moving 
Rover :mr1 is at [N 2 5]
Rover  (:mr1)  is moving 
Rover :mr1 is at [N 2 5]
Finished on Thread[main,5,main]
Rover  (:mr2)  is moving 
Rover :mr2 is at [E 1 5]
Rover  (:mr2)  is moving 
Rover :mr2 is at [N 1 6]

Upvotes: 2

Views: 996

Answers (2)

skatenerd
skatenerd

Reputation: 60

This seems like a really good place to use Clojure's agent feature. I am not qualified to fully explain how to use them, but a really good example of their usage can be found here. Starting threads using agents is dead-easy, and I think it is more idiomatic.

The code would look something like,

(def rover1 (agent [mr1-posn mr1-movs mr1-id])) 
(def rover2 (agent [mr2-posn mr2-movs mr2-id])) 
(defn rover-behave [[posn movs id]]
  (send-off *agent* #'rover-behave)
  (. Thread (sleep 1000))
  (let [new-mov (determine-rover-move posn movs id)
        new-posn (posn-after-move posn new-mov)]
    ;return value updates state of agent
    [new-posn movs id]
  )
)
(send-off rover1 rover-behave)
(send-off rover2 rover-behave)

Upvotes: 1

Gert
Gert

Reputation: 3859

Take a close look at these two lines:

(.start (Thread. (rover-thread mr1-id mr1-movs update-work-block)))
(.start (Thread. (rover-thread mr2-id mr2-movs update-work-block))))))

This code evaluates the (rover-thread mr1-id mr1-movs update-work-block) first, and passes the result of that to the constructor of Thread, which is not what you want.

Here's a simple function to illustrate the principle. This doesn't work, because the (f ...) is evaluated before its result it passed to the Thread constructor:

(defn run-thread-thing-wrong []
  (let [f (fn [n s]
            (doseq [i (range n)]
              (prn s i)
              (Thread/sleep (rand 1000))))]
    (.start (Thread. (f 10 "A")))
    (.start (Thread. (f 10 "B"))))
  nil)

Here's a version that does work. A function is passed to the Thread constructor instead:

(defn run-thread-thing []
  (let [f (fn [n s]
            (doseq [i (range n)]
              (prn s i)
              (Thread/sleep (rand 1000))))]
    (.start (Thread. (fn [] (f 10 "A"))))
    (.start (Thread. (fn [] (f 10 "B")))))
  nil)

Note: instead of (fn [] ....) you can use the short form #(....) for anonymous functions.

Here's another version that does the same, but with a future instead of manually creating threads:

(defn run-thread-thing []
  (let [f (fn [n s]
            (doseq [i (range n)]
              (prn s i)
              (Thread/sleep (rand 1000))))]
    (future (f 10 "A"))
    (future (f 10 "B")))
  nil)

Note that in this case, you pass a form to future instead of a function.

Upvotes: 7

Related Questions