3 ms·
Out of curiosity, how is this unfair/shallow? It's not obvious to me
by tln 6y ago
Out of curiosity, how is this unfair/shallow? It's not obvious to me
- platinumrad 6y agoGiven the domains where Go is most likely to be used, any comparison of goroutines vs threads should include the netpoller vs blocking socket i/o.
- derefr 6y agoWhy would using threads imply using [thread-]blocking socket IO? Most HLL runtimes (that you’d ever want to use for writing a server) have some async IO completion mechanism using scheduler-reserved “async IO waiter” threads, exactly akin to netpoll. (Or they plug into the OS in a less-portable way that avoids blocking IO syscalls to begin with, like Node’s libuv.) Or, if the language’s runtime doesn’t do it for you, then the popular connection-pooling abstractions that the language’s server libraries are built on top of do it for you, by spawning their own AIO completion threads. Jetty’s worker pool in Java, Tokio in Rust, etc. Honestly, who’s out there writing code in 2021 where there are worker POSIX threads, and those threads are (directly or indirectly) calling read(2)?
- matklad 6y agoI do :) I don’t write web services, so I have low O(1) things to communicate with, for which threads with blocking read work OK. They are not perfect, as there is no cancellation, but working around lack of cancellation in a couple of places is less costly than bringing dependency on a relatively fresh Rust async ecosystem.
- platinumrad 6y agoThreads and blocking calls is the programming model exposed by Go and the fact that it can do this efficiently is the few notable accomplishments of an otherwise unremarkable language. Many people find threads and blocking calls to be much easier to reason about than the async model of, for example, libuv.
- derefr 6y agoWell, I mean, you can eat your cake and have it too (i.e. have a language without the non-determinism of preemptively-scheduled green threads, while still having “blocking” call semantics.) .NET Orleans, for example: technically cooperative-scheduled tasks/fibers, but where yield points (i.e. generated state-machine transition points) are decided at compile-time; and where the inability to place such a yield-point within a soft-real-time-bounded distance from the previous one is a compile-time error. You don’t have to place the yield points yourself (like explicit async code), but they’re right there to see when you debug or disassemble the program, and you can use pragmas to adjust where or if they’re placed. Technically the Erlang runtime is this way as well (cooperatively-scheduled, with “explicit” yield points) but since one of those explicit yield-points is use of the CALL or RET ops—and since there are no looping ops, only tail-CALLs—it’s kind of hard to write an explicit atomic-CPU-blocking hard-realtime task-segment in Erlang. (You can, but you have to unroll all your loops. Which Erlang has no tooling support for. Much easier to just push the problem to C through a NIF. At least the Erlang scheduler has explicit design support for these long-running blocking NIF operations through “dirty schedulers”, so it’s not like you’re breaking the runtime’s operational tolerances by trying to do hard-realtime things in it.)
- matklad 6y agoI don’t think every benchmark should be a benchmark of everything. Targeted micro benchmarks are valuable, if they are explicit about their applicability scope. The post is very direct about looking only at the memory usage, and literally says that using the results to reason about overall performance would be wrong. I see how the article could be read as “goroutines bad, threads good”, as it doesn’t go to extraordinary lengths to prevent that. But I prefer to cater to a careful reader, and not add loud disclaimers repeatedly.
- platinumrad 6y agoYes, I think I read a little too much into your piece. I had on my mind at the time a certain prominent former Rust contributor's repeated bashing of M:N threading as well as the silly firestorm that erupted from that (admittedly not great) recent blog post critical of async Rust.