Concurrent Slices: Append Not Thread-Safe
Question:
Consider the following code that appends to a slice using multiple goroutines within a for loop:
destSlice := make([]myClass, 0) var wg sync.WaitGroup for _, myObject := range sourceSlice { wg.Add(1) go func(closureMyObject myClass) { defer wg.Done() var tmpObj myClass tmpObj.AttributeName = closureMyObject.AttributeName destSlice = append(destSlice, tmpObj) }(myObject) } wg.Wait()
On occassion, this code produces missing or blank data in destSlice or doesn't include all elements from sourceSlice. Why might this be occurring?
Answer:
In Go, all values are susceptible to data races when subject to concurrent read/write operations, including slices. This is due to the way that slice headers (which contain the information about the capacity and length of the slice) are implemented.
Running the code with the -race flag will confirm the presence of data races:
go run -race play.go
Solution:
To avoid data races and ensure concurrent safety when appending to a slice, a synchronization primitive such as a sync.Mutex should be used to guard the writing of the destSlice value:
var ( mu = &sync.Mutex{} destSlice = make([]myClass, 0) ) var wg sync.WaitGroup for _, myObject := range sourceSlice { wg.Add(1) go func(closureMyObject myClass) { defer wg.Done() var tmpObj myClass tmpObj.AttributeName = closureMyObject.AttributeName mu.Lock() destSlice = append(destSlice, tmpObj) mu.Unlock() }(myObject) } wg.Wait()
Alternatively, consider using channels to facilitate the concurrent appending process.
The above is the detailed content of Why is Appending to a Slice in Concurrent Goroutines Not Thread-Safe?. For more information, please follow other related articles on the PHP Chinese website!