将append()附加到另一个线程正在读取的切片是否安全?

时间:2016-10-27 21:38:24

标签: go

假设我有很多goroutines做这样的事情:

func (o *Obj) Reader() {
  data := o.data;
  for i, value := range data {
    log.Printf("got data[%v] = %v", i, value)
  }
}

一个人这样做:

func (o *Obj) Writer() {
    o.data = append(o.data, 1234)
}

如果data := o.data表示复制了切片的内部结构,这看起来可能是安全的,因为我永远不会修改副本的可访问范围内的任何内容。我要么在范围之外设置一个元素并增加长度,要么分配一个全新的指针,但读者将在原始指针上运行。

我的假设是否正确,这样做是否安全?

我知道切片一般不是“线程安全的”,问题更多的是slice1 := slice2实际复制了多少。

2 个答案:

答案 0 :(得分:4)

问题中的代码是不安全的,因为它在一个goroutine中读取变量并在没有同步的情况下修改另一个goroutine中的变量。

这是使代码安全的一种方法:

type Obj struct {
   mu sync.Mutex // add mutex
   ... // other fields as before
}

func (o *Obj) Reader() {
    o.mu.Lock()
    data := o.data
    o.mu.Unlock()
    for i, value := range data {
      log.Printf("got data[%v] = %v", i, value)
    }
}

func (o *Obj) Writer() {
     o.mu.Lock()
     o.data = append(o.data, 1234)
     o.mu.Unlock()
}

Reader对本地切片变量data的范围是安全的,因为Writer不会修改局部变量data或通过局部变量可见的后备数组data

答案 1 :(得分:0)

晚了一点,但是如果您的用例是频繁读取和不频繁写入,则atomic.Value旨在解决此问题:

type Obj struct {
  data atomic.Value // []int
  mu sync.Mutex
}

func (o *Obj) Reader() {
  data := o.data.Load().([]int);
  for i, value := range data {
    log.Printf("got data[%v] = %v", i, value)
  }
}

func (o *Obj) Writer() {
    o.mu.Lock()
    data := o.data.Load().([]int);
    data = append(o.data, 1234)
    o.data.Store(data)
    o.mu.Unlock()
}

这通常比MutexRWMutex快得多。

请注意,这仅适用于实际上是副本的数据,在这种情况下是这样,因为附加时您可以安全地维护对上一个切片的引用,因为append()会在扩展时创建新副本。如果您要更改分片的元素或使用其他数据结构,则此方法并不安全。