Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang concurrency: how to append to the same slice from different goroutines

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?

like image 542
Daniele B Avatar asked Aug 28 '13 22:08

Daniele B


People also ask

Are a way to synchronize the access of shared resources between Goroutines?

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.

Are slices concurrent safe?

In Go no value is safe for concurrent read/write, slices (which are slice headers) are no exception.

Does append create a new slice Golang?

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.

Can a Goroutine start a Goroutine?

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.


2 Answers

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.

like image 175
Volker Avatar answered Sep 17 '22 05:09

Volker


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) } 
like image 38
chris Avatar answered Sep 17 '22 05:09

chris