Concurrent read/write of different parts of a slice

1,253 views
Skip to first unread message

John C.

unread,
Jan 3, 2014, 5:04:03 PM1/3/14
to golan...@googlegroups.com
Is it safe (i.e. race-free) to concurrently read/write or write/write a slice when you are certain the slice indices are different?  I'm not worried about observability of the writes, just worried about corruption of the data.

For example, the following  (at http://play.golang.org/p/BOWoiHMH34) writes to both x[0] and x[1] in separate goroutines, and it is known that the indices are different.  However, I suppose there could be word-size contention issues if a 64-bit machine is trying to write to "different" 8-bit addresses that are actually in the same 64-bit word.

func main() {
x := make([]byte, 2)
done := make(chan bool)
go func() {
x[0] = 7
done <- true
}()
x[1] = 13
<-done
fmt.Print(x)
}


Thanks,
John C.

Jsor

unread,
Jan 3, 2014, 6:11:23 PM1/3/14
to golan...@googlegroups.com
FWIW, building that code with the -race flag doesn't give any warning, though it's possible it's a false negative. A slightly conceptually safer option might be to pass each goroutine a non-overlapping subslice containing the indices it will write to, like in here:


It's a little less clear with only one index, but say you have a slice of len 7, and you want one goroutine to write to indices 0-3 and the other from 4-6, so you pass one slice[0:4] and the other slice[4:]. This doesn't work if your criteria involve a stride (e.g. "I want one goroutine to write in even indices and one in odds"), but I think in most cases using subslices for this purpose is safer and more self explanatory.

Dave Cheney

unread,
Jan 3, 2014, 6:18:41 PM1/3/14
to John C., golang-nuts
On Sat, Jan 4, 2014 at 9:04 AM, John C. <jscroc...@gmail.com> wrote:
Is it safe (i.e. race-free) to concurrently read/write or write/write a slice when you are certain the slice indices are different?  I'm not worried about observability of the writes, just worried about corruption of the data.

For example, the following  (at http://play.golang.org/p/BOWoiHMH34) writes to both x[0] and x[1] in separate goroutines, and it is known that the indices are different.  However, I suppose there could be word-size contention issues if a 64-bit machine is trying to write to "different" 8-bit addresses that are actually in the same 64-bit word.

Most machine models, well x86, allow different cpu's to write different bytes our of a word at a severe penalty due to cache line thrashing.
 

func main() {
x := make([]byte, 2)
done := make(chan bool)
go func() {
x[0] = 7
done <- true
}()
x[1] = 13
<-done
fmt.Print(x)
}

There are two camps on this matter, I am in the camp that done <- true does not imply a happens before relationship such that <- done will see x[0] == 7. I do not recommend this, use a mutex. 
 

Thanks,
John C.

--
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/groups/opt_out.

Caleb Spare

unread,
Jan 3, 2014, 6:25:00 PM1/3/14
to Dave Cheney, John C., golang-nuts
On Fri, Jan 3, 2014 at 3:18 PM, Dave Cheney <da...@cheney.net> wrote:



On Sat, Jan 4, 2014 at 9:04 AM, John C. <jscroc...@gmail.com> wrote:
Is it safe (i.e. race-free) to concurrently read/write or write/write a slice when you are certain the slice indices are different?  I'm not worried about observability of the writes, just worried about corruption of the data.

For example, the following  (at http://play.golang.org/p/BOWoiHMH34) writes to both x[0] and x[1] in separate goroutines, and it is known that the indices are different.  However, I suppose there could be word-size contention issues if a 64-bit machine is trying to write to "different" 8-bit addresses that are actually in the same 64-bit word.

Most machine models, well x86, allow different cpu's to write different bytes our of a word at a severe penalty due to cache line thrashing.
 

func main() {
x := make([]byte, 2)
done := make(chan bool)
go func() {
x[0] = 7
done <- true
}()
x[1] = 13
<-done
fmt.Print(x)
}

There are two camps on this matter, I am in the camp that done <- true does not imply a happens before relationship such that <- done will see x[0] == 7. I do not recommend this, use a mutex.

I cannot see how to reconcile your "camp" with the Go memory model as currently published.

"A send on a channel happens before the corresponding receive from that channel completes." http://golang.org/ref/mem#tmp_6

The example code in that section also seems to indicate that this is safe.

-Caleb

John Crockett

unread,
Jan 3, 2014, 6:34:28 PM1/3/14
to Caleb Spare, Dave Cheney, golang-nuts
For the record, my example of goroutines 

John Crockett

unread,
Jan 3, 2014, 6:35:14 PM1/3/14
to Caleb Spare, Dave Cheney, golang-nuts
... Was contrived. Only the slice issue was relevant. 

Dave Cheney

unread,
Jan 3, 2014, 6:40:19 PM1/3/14
to Caleb Spare, John C., golang-nuts


On 4 Jan 2014, at 10:25, Caleb Spare <ces...@gmail.com> wrote:



On Fri, Jan 3, 2014 at 3:18 PM, Dave Cheney <da...@cheney.net> wrote:



On Sat, Jan 4, 2014 at 9:04 AM, John C. <jscroc...@gmail.com> wrote:
Is it safe (i.e. race-free) to concurrently read/write or write/write a slice when you are certain the slice indices are different?  I'm not worried about observability of the writes, just worried about corruption of the data.

For example, the following  (at http://play.golang.org/p/BOWoiHMH34) writes to both x[0] and x[1] in separate goroutines, and it is known that the indices are different.  However, I suppose there could be word-size contention issues if a 64-bit machine is trying to write to "different" 8-bit addresses that are actually in the same 64-bit word.

Most machine models, well x86, allow different cpu's to write different bytes our of a word at a severe penalty due to cache line thrashing.
 

func main() {
x := make([]byte, 2)
done := make(chan bool)
go func() {
x[0] = 7
done <- true
}()
x[1] = 13
<-done
fmt.Print(x)
}

There are two camps on this matter, I am in the camp that done <- true does not imply a happens before relationship such that <- done will see x[0] == 7. I do not recommend this, use a mutex.

I cannot see how to reconcile your "camp" with the Go memory model as currently published.

"A send on a channel happens before the corresponding receive from that channel completes." http://golang.org/ref/mem#tmp_6

I agree, however x is not sent through the done channel so it's contents are not guaranteed to be visible to the receiver of the value passed over done. 

Caleb Spare

unread,
Jan 3, 2014, 6:41:00 PM1/3/14
to John Crockett, Dave Cheney, golang-nuts
Sorry for hijacking John.

As far as I know, your goroutines are touching different memory and this is not a race. But I'm very far from being an expert, so hopefully someone more knowledgeable will give a definitive answer.

I think I've even used this before in my own code as

results = make([]Result, N)
for i := range results {
  go func(i int) {
    results[i] = compute(...)
  }(i)
}

Maxim Khitrov

unread,
Jan 3, 2014, 7:25:26 PM1/3/14
to Dave Cheney, Caleb Spare, John C., golang-nuts
On Fri, Jan 3, 2014 at 6:40 PM, Dave Cheney <da...@cheney.net> wrote:
>> "A send on a channel happens before the corresponding receive from that
>> channel completes." http://golang.org/ref/mem#tmp_6
>
> I agree, however x is not sent through the done channel so it's contents are
> not guaranteed to be visible to the receiver of the value passed over done.

How would it be possible to implement a send operation without it also
being a memory barrier?

Dave Cheney

unread,
Jan 3, 2014, 7:40:08 PM1/3/14
to Maxim Khitrov, Caleb Spare, John C., golang-nuts
Sure. I agree.

However whenever this topic comes out the spec is quoted chapter and verse. My argument is the spec only mentions send and receives in the context of the value that is passed over the channel. It doesn't talk about memory barriers.

My reading of the spec allows the send on done to be reordered above the assignment to x[0] as that still allows the send to done to occur before the receive from done.

Kyle Lemons

unread,
Jan 3, 2014, 8:13:54 PM1/3/14
to Dave Cheney, Maxim Khitrov, Caleb Spare, John C., golang-nuts
The memory model is the guide here, not the spec.

Values sent over a channel are irrelevant, because the values are copied.  If somehow the memory pointed to by a pointer being sent through a channel were special, I think it would be very surprising.


Thomas Bushnell, BSG

unread,
Jan 3, 2014, 9:42:10 PM1/3/14
to Dave Cheney, Caleb Spare, John C., golang-nuts
On Fri, Jan 3, 2014 at 3:40 PM, Dave Cheney <da...@cheney.net> wrote:
I agree, however x is not sent through the done channel so it's contents are not guaranteed to be visible to the receiver of the value passed over done.

That's not what the model says. Everything goroutine does before sending is visible to any go routine that receives what was sent. The model makes no reference at all to the particulars of what happens to be sent through the channel.

It's perfectly legitimate, for example, to implement general purpose mutexes like this (though this is slower than the way the standard library does it):

type mutex chan bool

func (m mutex) lock() {
  <- m
}

func (m mutex) unlock() {
  m <- true

Dave Cheney

unread,
Jan 3, 2014, 9:50:36 PM1/3/14
to Thomas Bushnell, BSG, Caleb Spare, John C., golang-nuts
Thank you Thomas, if you can point me to the place in chan.c where the runtime enforces this property, i'll gladly conceded my position.

Keith Randall

unread,
Jan 4, 2014, 7:35:28 PM1/4/14
to golan...@googlegroups.com, Thomas Bushnell, BSG, Caleb Spare, John C.
Regarding the original post (read/write of different indexes of an array), the spec does not directly address this point.  However, it is certainly safe to do so in all current Go implementations.  Not since early Alphas were processors allowed to clobber writes by other processors to adjacent bytes.  If any such "feature" were ever to resurface, Go would probably have to make accommodations (use atomic ops or pad data types) to compensate.

John Crockett

unread,
Jan 4, 2014, 7:44:05 PM1/4/14
to Keith Randall, golan...@googlegroups.com, Thomas Bushnell, BSG, Caleb Spare
Thank you, Keith. That's exactly what I wanted to find out. 

John C.
Reply all
Reply to author
Forward
0 new messages