Benign Data Race and Undefined Behaviour

1,042 views
Skip to first unread message

Dan Mullineux

unread,
Mar 19, 2016, 2:25:53 PM3/19/16
to golang-nuts
tl;dr Is there such a thing as a benign data race in go?

In the past I've accepted benign races in high throughput code, for example - consider multiple feeds of stock quotes where the last value wins.

I take a similar approach in a go tracing library in a distributed system where subtle ordering differences non critical.

Recently having read this article: https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong I question the sanity of these earlier choices. However go generally has fewer undefined behaviour cases than C++ as far as I can tell, but now I'm concerned that these benign races are not so benign after all.

Harmless looking and well understood C++ code appears to be able to behave extremely unpredictably under race conditions.

Consider:

var a int = 0

func lazyinc() {
a = a + 1
}

Clearly a data race, but apparently obvious that after the statement a=a+1, a will always contain a value that is one more than any ‘happened before’ value of a (overflow ignored), even if many +1's have now been missed. 

Given  Dmitry Vyukovs article I understand how the above may release the demons, under certain compiler optimisations - at least in C/C++ - that the value of a can indeed be anything - all bets are off.

I was hoping to have found some evidence to the contrary in golang but have not been able to, other than in this thread https://groups.google.com/forum/#!topic/golang-nuts/MB1QmhDd_Rk where Ian Lance Taylor states:

“if your program has a race condition, the behaviour is undefined”

The Go Memory Model https://golang.org/ref/mem page details some subtle effects of a lack of synchronisation, but does nothing to explicitly answer my question, other than the humorous and wise: ‘don’t be clever’.

I understand that my approach is ill advised and an accident waiting to happen, and there is nothing wrong with synchronisation, and i should not be doing what I'm doing etc. etc.

However, Is Ian correct? Do I have to sadly accept that even the code:

a = a + 1 // avoided a++ in case that adds more noise to the heart of the question

...is fully undefined in Go in a data race, or If it happens to behave in unsurprising ways now, that there is no guarantee it will forever,  and that I have to crack open sync.atomic? 

Matt Harden

unread,
Mar 19, 2016, 4:02:54 PM3/19/16
to Dan Mullineux, golang-nuts
Yes. Ian is correct. Dmitry is correct. I know that Dmitry, in particular, is an expert who has thought very deeply about these issues. Ian is a respected member of the Go team. Others have said the same. Heed their advice. Even if it works correctly with your current compiler and platform, this is only a lucky accident and is not at all guaranteed by the Go language reference or memory model, or the Go 1 promise. If your program has a race condition, its behavior is undefined. Please note that this extends beyond just the variable(s) involved in the race. A program whose behavior is undefined may do anything at all, or nothing at all. There is no such thing as a benign data race.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Dan Mullineux

unread,
Mar 19, 2016, 8:44:18 PM3/19/16
to golang-nuts, dan...@gmail.com
Thanks Matt.

I certainly don't doubt Ian and Dmitrys pedigree. Dmitry's article was more about C++, and Ian's comment was kind of in passing. I wasn't sure it was a definitive statement. 

Sounds like you confirm that it is though. I suppose limited registers and the desire to keep things on die and as close the the CPU as possible makes this unavoidable.

I'll trust the compiler authors to optimise better than I can in my code and I'll also perhaps be a little less lazy.

Thanks.

Nick Craig-Wood

unread,
Mar 20, 2016, 7:19:52 AM3/20/16
to Dan Mullineux, golang-nuts
On 19/03/16 18:25, Dan Mullineux wrote:
> tl;dr Is there such a thing as a benign data race in go?

This has come up quite a few times before on this list (in one guise or
another), and the consensus seems to be no, there is no such thing as a
benign data race in go.

The corollary to this is that if you ask for help here your program
should not have any races detectable by the race detector.

In my own code, I take the attitude that the race detector knows better
than me and I always heed its advice!

--
Nick Craig-Wood <ni...@craig-wood.com> -- http://www.craig-wood.com/nick

Ian Lance Taylor

unread,
Mar 20, 2016, 1:19:03 PM3/20/16
to Dan Mullineux, golang-nuts
On Sat, Mar 19, 2016 at 5:44 PM, Dan Mullineux <dan...@gmail.com> wrote:
>
> I certainly don't doubt Ian and Dmitrys pedigree. Dmitry's article was more
> about C++, and Ian's comment was kind of in passing. I wasn't sure it was a
> definitive statement.

To be honest I'm not sure it's a definitive statement either. I do
think it's clearly best to write your programs to never have a race
condition. And I think there is a strong argument to be made that
race conditions should be treated as undefined behavior. But when it
comes to considering what the constraints on a Go implementation are,
it's possible that a Go implementation should not just give up in the
face of a race condition. It's possible that Go should require that a
read of a single memory word should always return some value written
to that word, though which value will be unpredictable, and no
necessary relationship is implied between that word and any other
word. If that idea is adopted, then I think in your `a = a + 1`
example the resulting a will always be at least one more than the
initial a, assuming correct happens-before relationships on both sides
(though I note that the example is not a great one, since have
sync/atomic/AddXX). This is an area that has not, I think, been fully
settled.

Ian

David Chase

unread,
Mar 21, 2016, 11:18:17 AM3/21/16
to golang-nuts

"a will always contain a value that is one more than any ‘happened before’ value of a"

Consider the following three threads, and what thread C observes:

time increases in in that direction ------->

a=0
A:   load a, t=a+1 [scheduling delay.] a=t
B:           (100 times) a=a+1 
C:                             a=a+1        a=?

C increments a from 100 to 101, but at the a=? point, C observes that a is 1

The safest real-world statement you can make is that "a will always contain a value one more than some happened-before value", not necessarily any that your thread ever observed.
And that's not even assuming future compiler magic; the OS could decide to reschedule the OS thread underneath a goroutine or Java thread just for the heck of it.



Dan Mullineux

unread,
Mar 22, 2016, 6:45:15 AM3/22/16
to golang-nuts
David yes 'any' or 'some' is the benign case. My concern is a reloaded spilled register from any other section/statement, not just my a=a+1 section.

I would be happy that 'a' is any value from 0..102 where you have 'a=?'

Ian, thanks for the authoritative reply. Whilst I personally would love Go to behave in the bounded (defined but none-deterministic) way you describe as one option, I also understand that it could limit what an optimising compiler can get away with, in return for allowing a developer to make an optimising decision in some rare situations. I also see that coding for benign races adds the burden of explicit use of // +build !race.

For now I'll run some benchmarks and inevitably synchronise, and avoid/remove races in the future.

Cheers.
Reply all
Reply to author
Forward
0 new messages