I have concurrent goroutines which want to append a (pointer to a) struct to the same slice. How do you write that in Go to make it concurrency-safe?
This would be my concurrency-unsafe code, using a wait group:
var wg sync.WaitGroup MySlice = make([]*MyStruct) for _, param := range params { wg.Add(1) go func(param string) { defer wg.Done() OneOfMyStructs := getMyStruct(param) MySlice = append(MySlice, &OneOfMyStructs) }(param) } wg.Wait()
I guess you would need to use go channels for concurrency-safety. Can anyone contribute with an example?
Another way to synchronize access to a shared resource is by using a mutex . A mutex is named after the concept of mutual exclusion. A mutex is used to create a critical section around code that ensures only one goroutine at a time can execute that code section.
In Go no value is safe for concurrent read/write, slices (which are slice headers) are no exception.
The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice.
It's perfectly acceptable to call a goroutine from another goroutine. The calling goroutine will still exit and the new goroutine will go on it's merry way. Show activity on this post. Spaning a new goroutine from within a goroutine is per se perfectly fine.
There is nothing wrong with guarding the MySlice = append(MySlice, &OneOfMyStructs)
with a sync.Mutex. But of course you can have a result channel with buffer size len(params)
all goroutines send their answers and once your work is finished you collect from this result channel.
If your params
has a fixed size:
MySlice = make([]*MyStruct, len(params)) for i, param := range params { wg.Add(1) go func(i int, param string) { defer wg.Done() OneOfMyStructs := getMyStruct(param) MySlice[i] = &OneOfMyStructs }(i, param) }
As all goroutines write to different memory this isn't racy.
The answer posted by @jimt is not quite right, in that it misses the last value sent in the channel and the last defer wg.Done()
is never called. The snippet below has the corrections.
https://play.golang.org/p/7N4sxD-Bai
package main import "fmt" import "sync" type T int func main() { var slice []T var wg sync.WaitGroup queue := make(chan T, 1) // Create our data and send it into the queue. wg.Add(100) for i := 0; i < 100; i++ { go func(i int) { // defer wg.Done() <- will result in the last int to be missed in the receiving channel queue <- T(i) }(i) } go func() { // defer wg.Done() <- Never gets called since the 100 `Done()` calls are made above, resulting in the `Wait()` to continue on before this is executed for t := range queue { slice = append(slice, t) wg.Done() // ** move the `Done()` call here } }() wg.Wait() // now prints off all 100 int values fmt.Println(slice) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With