5 ms·
One of my favorite things about Go is that it cuts through the "threads vs. events" debate by offering thread-style programming with event-style scaling using w
by crawshaw 9y ago
One of my favorite things about Go is that it cuts through the "threads vs. events" debate by offering thread-style programming with event-style scaling using what you might call green threads (compiler assisted cooperative multitasking that has the programming semantics of preemptive multitasking).
That is, I can write simple blocking code, and my server still scales.
Using event loop programming in Go would take away one of my favorite things about the language, so I won't be using this. However I do appreciate the work, as it makes an excellent bug report against the Go runtime. It gives us a standard to hold the standard library's net package to.
- hannofcart 9y ago<quote>That is, I can write simple blocking code, and my server still scales. Using event loop programming in Go would take away one of my favorite things about the language, so I won't be using this.</quote> If Go has or can emulate 'generators' a-la Python/Nodejs,then you can write synchronous looking,blocking-like code with event loops as well.
- giovannibajo1 9y agoThat is exactly what Go does by default. Any time a blocking operation is performed, Go either leaves the OS-level thread blocked there and switches away, or hand the blocking operation to an internal thread which is running epoll for the whole process. The end result is much easier than Python/NodeJs because there is no explicit "async/await" or deferred-style programming. You simply write linear code and any blocking operation (at the syscall level) is transparently handled.
- hannofcart 9y agoThanks for taking the time to write this response. The more I hear about Go's features, the more I seem to like it.
- wyager 9y agoFYI, this is not unique to Go. A number of languages implement lightweight threads in the same way (roughly), like erlang or Haskell. It’s all epoll or other efficient polling primitives under the hood.
- weberc2 9y agoI'm of the impression that there's an important difference between Go and Haskell's models--namely that Go is M:N threaded and Haskell is not; however, I don't entirely understand the significance of the difference, so hopefully someone else can comment and enlighten me.
- joel_ms 9y agoAre you sure about Haskell not having m:n threads? Because it's seems weird, considering GHC is listed as one of a few examples on wikipedia: https://en.m.wikipedia.org/wiki/Thread_(computing)#M:N_.28hybrid_threading.29 https://en.m.wikipedia.org/wiki/Thread_(computing)#M:N_.28hy...
- weberc2 9y agoNo, I'm not sure. :) I may have incorrectly assumed that the definition of M:N threads includes movable application threads (e.g., Go's scheduler can move goroutines from one kernel thread to another).
- pcwalton 9y agoIn other words, Go uses threads. There's nothing semantically unique to Go about this model.
- cdoxsey 9y agoGo uses an m:n thread model. Goroutines are multiplexed onto a smaller number of os-level threads. They're sort of like threads, but they have a simplified programming model (There is no thread-level storage for example).
- pcwalton 9y agoGoroutines are threads. They're just not kernel-level threads. There have been implementations of pthreads that used an M:N model, for instance.
- weberc2 9y agoFor my information, why is M:N so successful for Go and not for pthreads? Is M:N more practical for systems with a particular kind of garbage collector?
- pcwalton 9y agoIn my opinion, it's because the Go team put a ton of effort into getting M:N working and didn't rigorously evaluate any other alternatives in Go, once moving GC was implemented. I'm not convinced that 1:1 wouldn't have been a perfectly reasonable implementation strategy for Go.
- weberc2 9y agoThis is surprising. It seems like Go gets pretty high praise from all over for its threading model, and it seems like there are relatively few high-performance servers that are built with 1:1. Am I wrong about that? If not, what explains this?
- 9y ago
- pcwalton 9y agoIt doesn't really "cut through" the debate any more than any other implementation of threads does. The only difference between Go and plain old one-thread-per-connection is that regular threads run in the kernel, while Go threads run in userspace. That's not a semantic difference, only an implementation detail (a large detail, to be clear, but still an implementation detail). There were historical implementations of pthreads, such as NGPT, that used precisely the same model as Go, and they were abandoned because the advantages over 1:1 were not sufficient to justify the complexity.
- crawshaw 9y agoThe goroutine implementation scales, while other thread implementations (by default) do not. That's a semantic difference. A Go server can have millions of active goroutines with moderate resource use. You can achieve the same on Linux or Solaris using kernel threads, but you have to work at it. With Go you don't have to work at it, and it works on macOS and Windows and a few other OSs too. This is all comparisons between O(1) things, but the constant factor matters.
- pcwalton 9y ago> You can achieve the same on Linux or Solaris using kernel threads, but you have to work at it. By setting the thread stack size to a reasonable value. That's it. And, in fact, on 64-bit you often don't even need to do that. The difference you're describing is a difference in default thread stack sizes, which is hardly a paradigm shift. We're talking about one call to pthread_attr_setstacksize().
- crawshaw 9y agoIt's not nearly as simple as you claim. First: if you have an epoll loop it is also the cost of the thread context switch, which has definitely us in RPC systems using kernel threads. By contrast the goroutine gets scheduled onto the kernel thread that answered the poll, saving the switch. Second: as I alluded to earlier, linux and solaris can scale their kernel thread implementations, not all OSs can. My experiences with large numbers of threads on the BSDs and Windows (in years past admittedly) suggest other kernels don't have thread implementations designed to scale to such high numbers. Solving the problem in userspace means Go programs written in this style are portable across operating systems. Third: you can only adjust stack sizes down if you know your program always keeps its stacks small. If you depend on libraries you don't own in C/C++, that's a difficult assumption. Go grows the stacks, so if you hit some corner case where a small number of goroutines need some significant amount of stack, your program uses more memory, but typically keeps working. No need for careful (manual!) stack accounting. If all this were as easy as you say, we would still write nearly all our C/C++ servers using threads. We don't because it's not.
- tychver 9y agoThere's nothing really that special about Goroutines. Ruby also introduced Fibres in 2007. There's been some discussion of adding a more automatic M:N threading model to Ruby 3.