Live data from Hacker News

Clojure core.async and Go: A Code Comparison

blog.drewolson.org

11–20 of 81 posts

Re: Clojure core.async and Go: A Code Comparison

#11
post #5

Are the clojure threads actually lightweight? Thread/sleep is blocking so you'd need to occupy 10 threads right? If you wanted to sleep without blocking a real thread would you use an executor service to write to a channel after delay and block on that?

Author here. From what I've read/seen, the go blocks are lightweight thread-like processes multiplexed onto a thread pool. You may be correct about using Thread/sleep, ideally I would have used (timeout ...) and then pulled off the channel. However, I didn't want to introduce the concept of channels too early in the post, so I felt Thread/sleep worked as a compromise.

Thread/sleep will block the entire thread. The thread pool could create more threads, but core.async uses a fixed thread-pool, so, yeah, don't use Thread/sleep in a go block. A timeout channel would be the way to go, or, you can use an alternate core.async implementation, which is a (small) part of the Pulsar project: https://groups.google.com/forum/#!topic/clojure/1xxxTti6Vi0 (I'm the main author)

We will have full API and semantic compatibility when version 0.2 is released next week. Pulsar also has cluster distribution and an Erlang-like actor framework.

Because Pulsar uses instrumentation at the bytecode level, you have more freedom within Go blocks. You wouldn't use Thread/sleep in the Pulsar implementation, either, but Strand/sleep will do the job. It detects whether you're in a go block (implemented as a fiber in Pulsar), in which case it suspends the fiber but doesn't block the thread, or within a normal thread, in which case it will simply call Thread/sleep.

Re: Clojure core.async and Go: A Code Comparison

#12
post #5

Are the clojure threads actually lightweight? Thread/sleep is blocking so you'd need to occupy 10 threads right? If you wanted to sleep without blocking a real thread would you use an executor service to write to a channel after delay and block on that?

Author here. From what I've read/seen, the go blocks are lightweight thread-like processes multiplexed onto a thread pool. You may be correct about using Thread/sleep, ideally I would have used (timeout ...) and then pulled off the channel. However, I didn't want to introduce the concept of channels too early in the post, so I felt Thread/sleep worked as a compromise.

Yeah, Thread/sleep isn't quite what you want here:

(time (let [c (chan), n 1000] (dotimes [i n] (go (Thread/sleep 50) (>! c i))) (dotimes [i n] ("Elapsed time: 8412.25733 msecs"

(defmacro gosleep [millis] `((time (let [c (chan), n 1000] (dotimes [i n] (go (gosleep 50) (>! c i))) (dotimes [i n] ("Elapsed time: 91.278469 msecs"

ETA: for comparison, here's what happens if you actually make 1000 system threads:

(time (let [c (chan) n 1000] (dotimes [i n] (thread (Thread/sleep 50) (>!! c i))) (dotimes [i n] ("Elapsed time: 4183.669835 msecs"

Re: Clojure core.async and Go: A Code Comparison

#13

Are the first two examples equivalent? In the go version, an anonymous function is being declared and then called with the value of the outer 'i'. In the clojure version, it appears that the value of 'i' is part of the closure for that function. The go version does what it does to avoid a race condition, because the goroutines are being spun up in the background and it's highly likely that it will take longer to spin…

It's a difference of the language. In the Go version, i is a mutable variable that can be read and written to. In Clojure, it's an immutable bound value. The i of one iteration is not in any way directly linked to the i of another iteration.

Re: Clojure core.async and Go: A Code Comparison

#14
post #4

I have never used Go or Clojure, but this is the first time I have heard of Go as being verbose - or is that just in comparison to Clojure?

Depends on what you compare it to. Go doesn't have macros and idiomatically prefers C-style for loops to higher-level abstractions (except for the built-in "range" construct) for iteration. So in general Lisps like Clojure offer more opportunities for terse code. (Which style is easier to understand is of course a never-ending debate.)

Re: Clojure core.async and Go: A Code Comparison

#15
post #8

Are the first two examples equivalent? In the go version, an anonymous function is being declared and then called with the value of the outer 'i'. In the clojure version, it appears that the value of 'i' is part of the closure for that function. The go version does what it does to avoid a race condition, because the goroutines are being spun up in the background and it's highly likely that it will take longer to spin…

The i is scoped differently in clojure than in golang so it doesn't have the same potential pitfall. I think golang people consider their scoping here a mistake (and have tooling to check it?).

The Go race detector will generally catch this mistake at runtime.

Re: Clojure core.async and Go: A Code Comparison

#16
post #8

Are the first two examples equivalent? In the go version, an anonymous function is being declared and then called with the value of the outer 'i'. In the clojure version, it appears that the value of 'i' is part of the closure for that function. The go version does what it does to avoid a race condition, because the goroutines are being spun up in the background and it's highly likely that it will take longer to spin…

The i is scoped differently in clojure than in golang so it doesn't have the same potential pitfall. I think golang people consider their scoping here a mistake (and have tooling to check it?).

C# had the exact same pitfall and it was recognised years ago. It's unfortunate that it made it in to Go.

Re: Clojure core.async and Go: A Code Comparison

#17
post #10

Earlier quoted context omitted.

Author here. From what I've read/seen, the go blocks are lightweight thread-like processes multiplexed onto a thread pool. You may be correct about using Thread/sleep, ideally I would have used (timeout ...) and then pulled off the channel. However, I didn't want to introduce the concept of channels too early in the post, so I felt Thread/sleep worked as a compromise.

Yah, for demo purposes of wanting to show something taking awhile thread/sleep is very convenient. I have no idea, time.sleep in golang might actually block a real thread too. I'm just curious if blocking in a go block could starve your thread pool or if there is some magic being done by the macro to even correct for that?

There isn't. Thread/sleep will block the thread and potentially starve your thread pool. Be careful not to do it.

Re: Clojure core.async and Go: A Code Comparison

#18
post #12

Earlier quoted context omitted.

Author here. From what I've read/seen, the go blocks are lightweight thread-like processes multiplexed onto a thread pool. You may be correct about using Thread/sleep, ideally I would have used (timeout ...) and then pulled off the channel. However, I didn't want to introduce the concept of channels too early in the post, so I felt Thread/sleep worked as a compromise.

Yeah, Thread/sleep isn't quite what you want here: (time (let [c (chan), n 1000] (dotimes [i n] (go (Thread/sleep 50) (>! c i))) (dotimes [i n] ( "Elapsed time: 8412.25733 msecs" (defmacro gosleep [millis] `( (time (let [c (chan), n 1000] (dotimes [i n] (go (gosleep 50) (>! c i))) (dotimes [i n] ( "Elapsed time: 91.278469 msecs" ETA: for comparison, here's what happens if you actually make 1000 system threads: (time…

Thanks (and thanks to pron). I've updated the post to include a warning against using Thread/sleep in go blocks for "real" code.

Re: Clojure core.async and Go: A Code Comparison

#19
post #4

I have never used Go or Clojure, but this is the first time I have heard of Go as being verbose - or is that just in comparison to Clojure?

Golang is verbose, as modern languages go. In its idiom, it's actually a little more verbose than some modern, tight C (though less verbose than most C code, and much tighter than Java).

Re: Clojure core.async and Go: A Code Comparison

#20

Are the first two examples equivalent? In the go version, an anonymous function is being declared and then called with the value of the outer 'i'. In the clojure version, it appears that the value of 'i' is part of the closure for that function. The go version does what it does to avoid a race condition, because the goroutines are being spun up in the background and it's highly likely that it will take longer to spin…

It's a difference of the language. In the Go version, i is a mutable variable that can be read and written to. In Clojure, it's an immutable bound value. The i of one iteration is not in any way directly linked to the i of another iteration.

"The Value of Values"
Post reply on HN