Accessing Slices Made From Same Array Concurrently

285 views
Skip to first unread message

Kaveh Shahbazian

unread,
Apr 20, 2018, 11:26:08 PM4/20/18
to golang-nuts
Is it safe to access slices that shares a back array concurrently? (Assuming the length of slices will not change)


Louki Sumirniy

unread,
Apr 20, 2018, 11:53:57 PM4/20/18
to golang-nuts
I am pretty sure that you will run into problems with this. On one hand passing pointers could lead to a write after a read, and on the other hand with pass by value you could get an out of date value and then stomp over another process write. I'm pretty sure the race detector will flag this. You will have to write some kind of access lock when processes need to write to it to exclude this condition.

Ian Lance Taylor

unread,
Apr 21, 2018, 12:21:15 AM4/21/18
to Kaveh Shahbazian, golang-nuts
It's safe to read from them concurrently. It's not in general safe to
modify them concurrently.

Ian

Kaveh Shahbazian

unread,
Apr 21, 2018, 5:00:53 AM4/21/18
to golang-nuts
@ Louki Sumirniy
Slices are values AFAIK. There is no passby pointer.

And the point is, race detector does not flag anything: https://play.golang.org/p/NC8mBwS1-0P

Ankit Gupta

unread,
Apr 21, 2018, 9:30:22 AM4/21/18
to golang-nuts
@Kaveh

Slices are values but they refer to the same back array location. You have created localized v which is appended inside goroutine which refer to a location containing its own byte array of len=10. So, you are not really referencing the same memory location as other v slice in the goroutine. You will be affected if you remove k,v:=k,v or append more than 10 bytes to v inside goroutine which will take up space on next slice's bytes. 

Louki Sumirniy

unread,
Apr 21, 2018, 10:31:25 AM4/21/18
to golang-nuts
Unless you pass pointers in Go, every time you hop in and out of a new scope any changes are discarded. This is why unless you type-bind with pointers you don't actually have an OOP method, as the function will not act upon the parent variable/structure.

I think if you change your playground code to pass pointers into the goroutines you'll either see race detector or clobbering.

Kaveh Shahbazian

unread,
Apr 21, 2018, 12:40:04 PM4/21/18
to golang-nuts
@Ankit That's what I thought. Yet the code is accessing the same underlying array. That is the part that worries me and -race does not complain.

@Louki Still no complain from -race! https://play.golang.org/p/dUt0QE63RDK

silviu...@gmail.com

unread,
Apr 21, 2018, 9:11:11 PM4/21/18
to golang-nuts
Hi Kaveh,

Change the line:
*ptr = append(*ptr, []byte(fmt.Sprintf("%02d", k1))...)

to
*ptr = append(*ptr, []byte(fmt.Sprintf("%15d", k1))...)

The buckets will overlap (more than 10 bytes) and you will get the race triggered in the detector

Silviu

Kaveh Shahbazian

unread,
Apr 22, 2018, 7:56:48 AM4/22/18
to golang-nuts
@Silviu

But no slice expansion is happening.

silviu...@gmail.com

unread,
Apr 22, 2018, 9:45:04 AM4/22/18
to golang-nuts
Kaveh, for this particular circumstance, based on your playground snippet, that's why you got no race. 

As per the original 2013 race detector blog post, "Because of its design, the race detector can detect race conditions only when they are actually triggered by running code, which means it's important to run race-enabled binaries under realistic workloads."

Kaveh Shahbazian

unread,
Apr 22, 2018, 4:20:40 PM4/22/18
to golang-nuts
Thanks Silviu,

Even when accessing the array directly in a concurrent manner, -race does not complain: https://play.golang.org/p/l-c2aPeOGwF

So, is this assumption correct? : As long as two goroutines are not modifying the same item in an array, accessing it is safe.

The initial intent was to implement a buffer pool; large arrays providing fixed length slices of (for example) 4KB length - and if the length of a message was above that, a slice would be allocated separately.

silviu...@gmail.com

unread,
Apr 22, 2018, 7:19:40 PM4/22/18
to golang-nuts
your assumption: "As long as two goroutines are not modifying the same item in an array, accessing it is safe."  is not entirely correct, or is incomplete. 
Even when only one goroutine is writing to that item, and a different one or more are reading, you still got a race, and the race detector will flag it. E.g. with 10 million iterations: https://play.golang.org/p/bpUQEyZMMy4

Tamás Gulácsi

unread,
Apr 23, 2018, 1:36:38 AM4/23/18
to golang-nuts
If there's no uncoordinated write and read/write of the same slot, then it's race-free.

Only reads does not need coordination.

I'm using a pattern alike: allocate a slice for the results, start the goroutines, each writing into it's own slot, then wait all of them to complete, and use the result array.
Here the "wait all" is crucial!

Louki Sumirniy

unread,
Apr 23, 2018, 1:46:56 AM4/23/18
to golang-nuts
There does still need to be a mutex for reads on an element that is being written to. It's a minor edge case but it could cause a serious problem when it hoppens.

Kaveh Shahbazian

unread,
Apr 23, 2018, 2:21:05 AM4/23/18
to golang-nuts
@Silviu The code is mutating same item from two goroutines. While the original target is to create a buffer pool that their items is not being mutated from two goroutines - actually they can not do that because at a specific time only one goroutine has access to one buffer.

@Tamas Regarding "If there's no uncoordinated write and read/write of the same slot, then it's race-free", is it safe to use slices with a shared underlying array, to be mutated from different goroutine? Every goroutine has access to one slice and that slice is only accessible to that one goroutine - until the goroutine is done and returns the slice to the pool.

Kaveh Shahbazian

unread,
Apr 23, 2018, 5:38:23 AM4/23/18
to golang-nuts
Also -race does not complain about this: https://play.golang.org/p/IeA4npcemf5

roger peppe

unread,
Apr 23, 2018, 9:53:09 AM4/23/18
to Kaveh Shahbazian, golang-nuts
You original example has a problem that others have pointed out because
it's possible for one goroutine to step on the part of the backing array used
by others. In principle though your technique looks OK to me as long
as you prevent that happening, for example by using the three-value slice
operator to set the capacity as well as the length of the bucket items.

https://play.golang.org/p/vzQlULC1zs7

It's OK to read and write items of the same underlying array as long
as any given element of the array is only accessed by a single goroutine
(or appropriate synchronisation is used).
> --
> 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.

Kaveh Shahbazian

unread,
Apr 23, 2018, 1:18:20 PM4/23/18
to golang-nuts
@rog Thanks! Three-value slice operator was a very nice hint.

The original intention was to implement a buffer-pool (pool of []byte) without fragmenting the memory. But I was not sure if I was doing it right.

Seems now, assuming the pool consumers are polite goroutines, this can be a good solution. The pool will be used in a TCP server.

Kaveh Shahbazian

unread,
Apr 24, 2018, 4:51:31 AM4/24/18
to golang-nuts
Final code is here: https://github.com/dc0d/bufferpool

I hope I got it right!

Mandolyte

unread,
Apr 24, 2018, 7:44:19 AM4/24/18
to golang-nuts
While not quite the same problem, this post describes something similar in concept, "banking strings" in a single large byte slice to avoid GC pressures.

https://syslog.ravelin.com/whats-all-that-memory-for-e89522e1c2c6
Reply all
Reply to author
Forward
0 new messages