Go concurrency <i>is</i> just threads. It's a particularly idiosyncratic userland implementation of them.<p>There are two claims here I'd like to unpack further:<p>1. "I've measured goroutine switching time to be ~170 ns on my machine, 10x faster than thread switching time." This is because of the lack of switchto support in the Linux kernel, not because of any fundamental difference between threads and goroutines. A Google engineer had a patch [1] that unfortunately never landed to add this support in 2013. Windows already has this functionality, via UMS. I would like to see Linux push further on this, because kernel support seems like the right way to improve context switching performance.<p>2. "Goroutines also have small stacks that can grow at run-time (something thread stacks cannot do)." This is a frequent myth. Thread stacks can do this too, with appropriate runtime support: after all, if they couldn't, then Go couldn't implement stack growth, since Go's runtime is built in userland on top of kernel threads. Stack growth is a feature of the <i>garbage collection infrastructure</i>, not of the concurrency support. You could have stack growth in a 1:1 thread system as well, as long as that system kept the information needed to relocate pointers into the stack.<p>Goroutines <i>are</i> threads. So the idea the "Go has eliminated the distinction between synchronous and asynchronous code" is only vacuously true, because in Go, everything is synchronous.<p>Finally, Go doesn't do anything to prevent data races, which are the biggest problem facing concurrent code. It actually makes data races <i>easier</i> than in languages like C++, because it has no concept of const, even as a lint. Race detectors have long existed in C++ as well, at least as far back as Helgrind.<p>[1]: <a href="https://blog.linuxplumbersconf.org/2013/ocw/system/presentations/1653/original/LPC%20-%20User%20Threading.pdf" rel="nofollow">https://blog.linuxplumbersconf.org/2013/ocw/system/presentat...</a>
> I'm happy to go on record claiming that Go is the mainstream language that gets this really right. And it does so by relying on two key principles in its core design...<p>The unmentioned third principle that it relies on is: "Curly braces, so it looks almost like C if you squint". That's what makes a language "mainstream" these days.<p>It looks like Go is very good at concurrency, but from everything I've read, I don't see how it's any better than Erlang or Clojure. The only controversial part of Eli's claim is the implication that other languages that get concurrency right aren't "mainstream". That's not a well-defined term and so naturally this is going to irk many people.<p>Perhaps the title would have been more accurate as "Go hits the concurrency nail on the head, using the hammer of K&R style". :-)
I think the author might be overstating how unique Go’s position is in terms of making concurrency easier. Haskell has the best concurrency story of any language I’ve used. Super lightweight green threads, simple concurrency primitives like MVar and STM, good performance (possibly requiring tweaks, but not bad out of the box). Referential transparency (by which I basically mean immutable data) makes sharing data across threads much easier and in some cases more performant by allowing you to avoid copying. Plus, you have a type system which makes it much easier to reason about your code.<p>All that being said, I haven’t written Go and can’t compare the two. Also, Haskell doesn’t support the actor/message passing model out of the box (although libraries exist for it) or prevent deadlocks (although the immutability helps a great deal here). BEAM languages, clojure, rust, pony and others all have their strengths here — again, this doesn’t discredit the article at all but the idea that Go is the clear winner is debatable.
According to the author Go make concurrent programming "the best experience, by far, compared to other popular programming languages today."<p>I beg to differ. I fail to see why I should choose Go over Elixir/Erlang for concurrency. Elixir's cuncurrency mechanisms are at least as good — and I would argue better — than Go's, and Elixir as a language has an expressiveness that Go lacks.
> <i>Programming with threads is hard - it's hard to synchronize access to data structures without causing deadlocks; it's hard to reason about multiple threads accessing the same data, it's hard to choose the right locking granularity, etc.</i><p>That's a list of problems that are specific to mutable state that is shared among threads.<p>As the old saying goes, "If it hurts, don't do it."<p>We've had ways of doing multithreaded code that are easier to reason about for <i>decades</i>. They really do work quite well. Why people doggedly insist on pretending they don't exist is a perennial mystery to me. Even if your programming language wasn't kind enough to include a better concurrency model in its standard library, there are always third-party libraries.<p>I realize my experience isn't universal, but, personally, I've discovered that there's precisely one scenario where I ever need to resort to code that involves mutexes: When the business requirements and the performance profiler have conspired to kidnap my children and hold them for ransom.
Its fairly interesting that this article doesn't mention actors, futures/promises and async/await style co-routines which are all extremely available in all of the major languages available today and broadly used (with the possible exception of golang).<p>Frankly, I think the concurrency story is one of the <i>weaknesses</i> of golang. Contrary to what this article says you cannot stop thinking about concurrency in your code in golang like you can in some of the more avante garde concurrency models (STM) and it doesn't provide nearly the sophistication in type or post build checking of other languages.
Don't disagree with the challenges about other languages being equally applicable. My first thought was also "Erlang does it at least as well as Go". Reasonable challenges on whether Erlang (or Elixir/Pony/...) are mainstream though.<p>For me the more valuable point is not that Go specifically gets it right: it's that async - as implemented in javascript/python/java/c# and so on - is fundamentally wrong. These two quotes get to the heart of it:<p>>The core idea is that in the asynchronous model we have to mentally note the blocking nature of every function, and this affects where we can call it from.<p>>The fundamental issue here is that both Python and C++ try to solve this problem on a library level, when it really needs a language runtime solution.<p>I've said for a while that async as implemented in javascript et al is the "GOTO" of concurrency - and should be considered equally as harmful as Dijkstra's observation on GOTO, for many of the same reasons [0].<p>[0] <a href="https://en.wikipedia.org/wiki/Considered_harmful" rel="nofollow">https://en.wikipedia.org/wiki/Considered_harmful</a>
Do people really find CSP a good approach to concurrency? I've always thought that channels were a relatively poor choice of fundamental primitive -- channel-based concurrency is tricky to get right and not very composable.<p>Most viable concurrency primitives are in some sense equivalent (you can build condition variables out of channels and vice-versa, say) but that doesn't mean they're equally good.<p>Java has per-object "synchronized" and "notify" as its core constructs, but it's a bad idea to use those directly for day-to-day concurrency tasks. Much better to use library classes like ThreadPoolExecutor and Future.<p>In Go, do you tend to use channels directly, or do you use higher-level wrappers?
Regarding one of the last comments, I'd say the two biggest reasons for using Node over Go, at least initially. Prototyping speed, and a stronger connection to a web front-end. I've really not seen any other language/platform work faster for developing a huge variety of implementation details than JS/Node. IT's a really good balance of performance, flexibility and ease of development.<p>Is it a Panacea? Of course not. That said, I think that starting more monolithic an breaking pieces off as needed is a strong approach, best started with Node. From there, you want different pipelines/processes/queues/workers in other platforms, great. Write that piece in go+grpc and call it from your api endpoint server.<p>So many times I see devs want to go the optimal X, without even considering if "optimal" is needed, and if it's prudent to start with.
For me, "great tools like the race detector" sums up the article - the detector is fallible, and although every point in the article has some validity, they also could all be argued against, and the result is a bit of a house of cards. So for me, Go at work by order, Erlang at home by choice.
<i>Mixing threads with event loops is possible, but so complicated that few programmers can afford the mental burden for their applications.</i><p>This is just Apple's Grand Central Dispatch model, or the event loops used internally in Chromium. It's not complicated at all, it's a very practical and productive approach.
> Mixing threads with event loops is possible, but so complicated that few programmers can afford the mental burden for their applications.<p>Correct me if I'm wrong, but doesn't basically every UI framework from the last 20 years do exactly this?
> Proper use of channels removes the need for more explicit locking<p>If you're lucky. Sharing mutable state is unsafe by default (map writes can crash!) yet very common and the language doesn't help you avoid it. A good language for concurrency would also make it easy to switch between sync and async method calls; the trouble with channels is they don't support passing errors and panics without rewriting everything to wrap them in a pseudo-generic struct.
Leave aside the extremely interesting engineer behind th go scheduler and goroutine.<p>The abstraction that goroutine provides is a simple, indipendent, isolated unit of execution.<p>You start it, and that is all you can do with it.<p>No way to set priorities, decided when to stop it or inspect it.<p>After the goroutine start the only interface that you get is a channel where to push and pop stuff from. Which is just too limited consider the complete lack of genetics.<p>It is really the best we can come up with?
> Programming with threads is hard - it's hard to synchronize access to data structures without causing deadlocks; it's hard to reason about multiple threads accessing the same data, it's hard to choose the right locking granularity, etc.<p>It's almost as if we need a language that has a focus on data races, concurrency and fearless threading.
I disagree. You really need "shared nothing" and messages. The only language that really hits the concurrency nail on the head is Erlang, which we use extensively in our business.