dpp66953
dpp66953
2017-05-24 08:27
浏览 16
已采纳

追加不是线程安全的吗?

I noticed that if I tried appending to a slice using goroutines inside a for loop, there would be instances where I would get missing/blank data:

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()

Sometimes, when I print all AttributeNames from destSlice, some elements are empty strings (""), and other times, some elements from sourceSlice are not present in destSlice.

Does my code have a data race, and does this mean that append is not thread-safe for concurrent use by multiple goroutines?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

2条回答 默认 最新

  • dongpai2754
    dongpai2754 2017-05-24 08:32
    已采纳

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

    Yes, your code has data races. Run with the -race option to verify.

    Example:

    type myClass struct {
        AttributeName string
    }
    sourceSlice := make([]myClass, 100)
    
    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()
    

    Running it with

    go run -race play.go
    

    Output is:

    ==================
    WARNING: DATA RACE
    Read at 0x00c420074000 by goroutine 6:
      main.main.func1()
          /home/icza/gows/src/play/play.go:20 +0x69
    
    Previous write at 0x00c420074000 by goroutine 5:
      main.main.func1()
          /home/icza/gows/src/play/play.go:20 +0x106
    
    Goroutine 6 (running) created at:
      main.main()
          /home/icza/gows/src/play/play.go:21 +0x1cb
    
    Goroutine 5 (running) created at:
      main.main()
          /home/icza/gows/src/play/play.go:21 +0x1cb
    ==================
    ==================
    WARNING: DATA RACE
    Read at 0x00c42007e000 by goroutine 6:
      runtime.growslice()
          /usr/local/go/src/runtime/slice.go:82 +0x0
      main.main.func1()
          /home/icza/gows/src/play/play.go:20 +0x1a7
    
    Previous write at 0x00c42007e000 by goroutine 5:
      main.main.func1()
          /home/icza/gows/src/play/play.go:20 +0xc4
    
    Goroutine 6 (running) created at:
      main.main()
          /home/icza/gows/src/play/play.go:21 +0x1cb
    
    Goroutine 5 (running) created at:
      main.main()
          /home/icza/gows/src/play/play.go:21 +0x1cb
    ==================
    ==================
    WARNING: DATA RACE
    Write at 0x00c420098120 by goroutine 80:
      main.main.func1()
          /home/icza/gows/src/play/play.go:20 +0xc4
    
    Previous write at 0x00c420098120 by goroutine 70:
      main.main.func1()
          /home/icza/gows/src/play/play.go:20 +0xc4
    
    Goroutine 80 (running) created at:
      main.main()
          /home/icza/gows/src/play/play.go:21 +0x1cb
    
    Goroutine 70 (running) created at:
      main.main()
          /home/icza/gows/src/play/play.go:21 +0x1cb
    ==================
    Found 3 data race(s)
    exit status 66
    

    Solution is simple, use a sync.Mutex to protect writing the destSlice value:

    destSlice := make([]myClass, 0)
    mux := &sync.Mutex{}
    
    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
            mux.Lock()
            destSlice = append(destSlice, tmpObj)
            mux.Unlock()
        }(myObject)
    }
    wg.Wait()
    

    You could also solve it in other ways, e.g. you could use a channel on which you'd send the value to be appended, and have a designated goroutine receiving from this channel and do the append.

    点赞 评论
  • dougaojue8185
    dougaojue8185 2018-07-12 13:52

    To give a more recent solution to this problem, it looks like Go has released a new map for sync purposes:

    https://godoc.org/golang.org/x/sync/syncmap

    点赞 评论

相关推荐