So what I personally find interesting about this code is how it just... asychronously kicks off arbitrary functions. It reads like scripting except it isn't.
I ask out of curiosity: are there other statically typed languages where you can do stuff like this? Java has Futures, but those are far more heavyweight. Clojure has concurrency constructs which are pretty close, but Clojure is dynamic. I only know a little bit of Haskell, so there might be something like this in there.
I used time.After recently to timeout a net.Dial call if it doesn't connect within 0.1s (it's an intranet app, connection time should be < 0.01s). While that solves my problem of not waiting too long for net.Dial, I wish there was a way to cancel a running go func() call somehow.
I want to read data from a remote port every second, with a 0.1s timeout. Any longer and I can treat it as a failure. If I put ReadRemotePort() in a forever loop with Sleep(1 second), even though the timeout will cause a failure as expected in 0.1s, there will be go func() threads still active until net.Dial has timed out. So at any given time, I could have 180 go func() calls just waiting for net.Dial to timeout at 3 minutes. I still haven't found a way to avoid this.
Edit: In practice, this isn't really a problem at all unless you're calling Dial something like a hundred or thousand times a minute and they're all timing out. Goroutines are very cheap. Still, it is an issue and will hopefully be resolved in Go1.1: http://code.google.com/p/go/issues/detail?id=2631
Q. What are the limits to scalability with building a system with many goroutines?
The primary limit is the memory for the goroutines. Each goroutine starts with a 4kB stack and a little more per-goroutine data, so the overhead is between 4kB and 5kB. That means on this laptop I can easily run 100,000 goroutines, in 500 MB of memory, but a million goroutines is probably too much.
For a lot of simple goroutines, the 4 kB stack is probably more than necessary. If we worked on getting that down we might be able to handle even more goroutines. But remember that this is in contrast to C threads, where 64 kB is a tiny stack and 1-4MB is more common.
But remember that this is in contrast to C threads, where 64 kB is a tiny stack and 1-4MB is more common.
Yeah not really. C threads normally have a fixed-size contiguous virtual memory space allocated to them, but the space they don't use is not actually consumed. You can even call madvise() to release memory on returning from a deep stack so you only actually "use" 4k of memory afterwards. The only significant resource consumed is virtual address space, which is effectively unlimited on 64-bit systems (why design a new language around 32-bits systems?). For that matter "C threads" could use segmented stacks as well if they wanted to.
But "goroutines" also have to be concerned with the total stack space they may use. For example, suppose those 100,000 goroutines were network servers and a hostile client could manipulate the connection to block the routine while it was consuming 400 KiB of stack. Now an attacker (or random confluence of events) can cause your program that normally averages 1% memory to use 100x as much as normal and run out of memory.
Space is almost never a reason to use coroutines over threads.
>For example, suppose those 100,000 goroutines were network servers and a hostile client could manipulate the connection to block the routine while it was consuming 400 KiB of stack.
hmm so you're saying a request causes 400KB to be allocated, and an attacker hits your server with 100k requests designed to cause your server to stall? The same criticism could be leveled against... any system where a requestor can allocate memory and stall a handler that is independent of the incoming request loop. How would this be different in a different concurrency model?
>Space is almost never a reason to use coroutines over threads.
I wouldn't necessarily say so. I have a polling service that uses a few thousand standing goroutines. The memory overhead for each individual goroutine is so low that I can just spawn them casually. Each goroutine only actually does work for a few seconds every few minutes; the vast majority of the time, each goroutine is spent sleeping. For this type of problem, I find that organizing things with goroutines and channels makes my code very easy to reason about. That, to me, is the biggest win of Go's concurrency model; that it's very easy to reason about.
> I find that organizing things with goroutines and channels makes my code very easy to reason about. That, to me, is the biggest win of Go's concurrency model; that it's very easy to reason about.
Absolutely agree. I just wish the net.Dial calls would let me specify a timeout and wouldn't hang around for 3mins in case something is down.
How would this be different in a different concurrency model?
That was actually the point. You have the same space considerations. Being coroutine instead of native thread is just a small constant factor... if this factor is important then you are almost certainly doing it wrong.
I wanted to query 100 remote ports each second. Since it takes less than 0.01s to make a successful call and get the 2-3 bytes of data from each server, I thought doing 100 calls in parallel wouldn't be an issue via goroutines. But if 100 x 180 goroutines are waiting to die any given second when there are prolonged connectivity issues (which are possible in my environment), I don't know how stable the app would be overall.
This annoyed me when dealing with net/http the other day. Really seems like the Go team hasn't spent much time thinking about problems on the client side of connections yet.
This isn't true. First net and net/http are great. Second, there are examples all over the ML (even within the last three days) that shows how to create an http client with a timeout-able Dial() function.
james4k just posted a link to the source where the comment by Brad Fitzpatrick actually says it's broken in the manner under discussion.
// TODO(bradfitz): the timeout should be pushed down into the
// net package's event loop, so on timeout to dead hosts we
// don't have a goroutine sticking around for the default of
// ~3 minutes.
I ask out of curiosity: are there other statically typed languages where you can do stuff like this? Java has Futures, but those are far more heavyweight. Clojure has concurrency constructs which are pretty close, but Clojure is dynamic. I only know a little bit of Haskell, so there might be something like this in there.