
Understanding Real-World Concurrency Bugs in Go [pdf] - oldgun
https://golangweekly.com/link/59972/b208593eda
======
c61
The two key takeaways for me are:

(1) "Contrary to the common belief that message passing is less error-prone,
more blocking bugs in our studied Go applications are caused by wrong message
passing than by wrong shared memory protection."

(2) "Shared memory synchronization operations are used more often than message
passing, ..."

So basically, (1) people have more trouble writing correct multithreaded
routines using message passing, and (2) in large production applications
people tend to fall back to using shared-memory primitives rather than
message-passing anyway.

It seems the intention of the Go language designers was to make a language
that would be simple and easy for programmers to pick up. Given that most
programmers are already accustomed to multithreaded programming using shared
memory and that this is conceptually simpler, I think the language designers
made a mistake by throwing channels, a relatively new and experimental
approach (yes, I know CSP is from 1978, but I'm talking about widely-adopted
industrial languages), into the language. I think it was the single biggest
mistake in the design of Go.

~~~
bb88
I feel like Golang is turning out to be like C++ with all of it's gotchas.
It's not that it didn't have good intentions, it's just they didn't nail the
corner cases for the end coder.

There's a lot of features of C++ that would have been awesome, had it not been
for those corner cases.

~~~
naikrovek
The ratio of gotchas in Go compared to C++ has to be on the order of 1:10,000

------
zzzcpan
This is pretty good and deserves more attention. Github makes bug studies so
much more approachable. Hopefully the study will make people more cautious
about Go's concurrency model, which is as error prone as shared memory
multithreading, if not more.

~~~
weberc2
What concurrency model do you prefer? Seems like it’s easy enough to opt into
a share-nothing model in Go, and this is generally what I do unless I have
performance concerns (and as a consequence, I very rarely see concurrency
bugs). I’m also not sure how much static analysis a la Rust could help the
problem without accepting Rust levels of productivity.

~~~
pcwalton
The only foolproof way to opt into shared nothing in Go is not to spawn any
goroutines. Global variables (which are all mutable, because there aren't any
other kinds of variables in Go) are used all over the place in Go, including
in the standard library.

~~~
tptacek
How many of the concurrency bugs in this paper trace to the combination of
"share-nothing" designs (channels) with mutable global variables in the
standard library? If the answer is "none", what evidence does this paper
present that mutable globals in the standard library are a practical
impediment to "share-nothing" designs in real programs?

~~~
pcwalton
I mean, standard library global variables not being thread-safe would
generally be treated as bugs in the standard library, so I wouldn't expect to
see bugs there.

The point is that shared-nothing designs are just not how Go typically works.
You can see that in functions like http.HandleFunc() (and everything else that
uses the DefaultServeMux), which registers a global handler across all threads
of the program.

~~~
tptacek
Is that a good example of impediments to correctness in shared-nothing designs
in Go? Using the default mux makes it hard for two different services to share
the same Go program, and reduces the flexibility of libraries, but it doesn't
appear to harm correctness or drag programs into shared memory designs. It
seems like more of a purity test than a practical critique.

~~~
pcwalton
But the default serve mux _is_ shared memory (it's literally a mutable global
variable in net/http). If it isn't shared memory, what is?

I mean, Go doesn't force you to use shared memory; no language with threads
does. It encourages it through library design (e.g. the default serve mux) and
language design (e.g. package init() functions) though.

~~~
tptacek
Right, I'm not disagreeing with you that the Go standard library and a few
idiomatic Go standard library transactions use shared memory. I'm disputing
that these are real impediments to building programs that benefit from shared-
nothing designs. Obviously, Go does a better job of _minimizing_ shared memory
than _eliminating_ it. I'm asking: does this distinction matter in practice?

~~~
pcwalton
There's Go functionality such as pprof that requires the use of the default
serve mux and therefore you can't use pprof with a true shared nothing design.
The log module is similar.

Does that lead to more bugs in programs? I don't know. It's quite possible it
doesn't matter in terms of defect count. It's not shared nothing, though, is
all I'm saying.

~~~
tptacek
Right, thus the distinction I'm making between purity test concerns and
practical concerns. Go is not, and has never claimed to be, a pure share-
nothing design. It's pretty up front about not being that!

------
Animats
Well, yes. Go's shared memory model is essentially the same as that of C and
C++. You have locks, and you have data objects, and the language has no idea
which lock covers what data. That's the big thing Rust got right.

Go's message passing model is somewhat error prone. It lets you pass
references. So you can create shared data through the channels. It's easy to
do this accidentally by passing slices, which are references to the underlying
array, across a channel.

More generally, Go channels are one-way. Since you often want an answer back,
you have to create some two-way mechanism from the raw one-way channels. It
seems to be common to do this at the raw channel level, rather than using some
"object" like encapsulation. Creating a mechanism which shuts down cleanly is
somewhat tricky, too.

------
crimsonalucard
"Surprisingly, our study shows that it is as easy to make concurrency bugs
with message passing as with shared memory, sometimes even more. For example,
around 58% of blocking bugs are caused by message passing. In addition to the
violation of Go’s channel usage rules (e.g., waiting on a channel that no one
sends data to or close), many concurrency bugs are caused by the mixed usage
of message passing and other new semantics and new libraries in Go, which can
easily be overlooked but hard to detect."

~~~
jstewartmobile
On the one hand, they're absolutely right.

On the other hand, statistical methods don't give quality it's due.

I'd rather fight a blocking bug than a data-updated-via-race-condition bug.

------
mirceal
confirms some of my suspicions to claims on how sexy and easy go is when it
comes to concurrency. if anything it will give you a false a sense of security
but god help you in case things go south and you’ve abused these features.

~~~
mirceal
reminded me of:
[https://twitter.com/pasiphae_goals/status/923835427995508741...](https://twitter.com/pasiphae_goals/status/923835427995508741?s=20)

------
tapirl
is there a html version?

------
crimsonalucard
As much as I don't like javascript, there is a whole swathe of concurrency
bugs that exists in golang and not in nodejs.

~~~
Thaxll
Makes sense since Nodejs has no parallelism features.

~~~
pcwalton
Go doesn't really have parallelism features either. A language designed for
parallelism needs to have data-parallel features like parallel iterators, good
support for SIMD, and concurrent data structures, neither of which Go has. (In
fact, due to lack of generics, you can't even build your own generic
concurrent data structures!)

~~~
tedunangst
Yikes. It's impossible to do parallel processing on my old pentium pro because
it lacks MMX?

~~~
pcwalton
I didn't say it was impossible to do parallel work in Go. You can get
parallelism in Node, too, by using workers, or by just forking processes and
having them communicate with pipes. Rather, what I was trying to emphasize is
that Go—and Node—aren't _designed_ for parallelism. Go has good support for
concurrency, not parallelism. Parallel languages have feature sets more like
CUDA, C++ with OpenMP, OpenCL, etc.

------
jstewartmobile
The terminology could use some refinement. Not sure "message passing" is the
right phrase to describe Go channels.

Most encounters I've had with "message passing"\--whether it was AJAX, Win32
PostMessage, or grade school--have been non-blocking. Go channels block.

edit: Here's a pretty good write-up from 2016 on why channels are an anti-
pattern:

[https://www.jtolio.com/2016/03/go-channels-are-bad-and-
you-s...](https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-should-
feel-bad/)

~~~
api
I don't see why blocking is an issue given that goroutines are light weight
threads. They are really cheap and can be created as needed, eliminating much
of the need for async patterns. It's one of the nicest things about Go given
that async is just manually implemented lightweight threading.

I would say though that go channels have not turned out to be as useful as I
thought they would be. I dont find myself using them that much. But maybe it's
just my style or the type of stuff I am writing.

~~~
pcwalton
> It's one of the nicest things about Go given that async is just manually
> implemented lightweight threading.

No, async/await is typically stackless, while goroutines have stacks. That's a
significant difference.

