从另一个线程正在读取的切片中使用append()方法是安全的吗?

huangapple go评论104阅读模式
英文:

Is is safe to append() to a slice from which another thread is reading?

问题

假设我有许多goroutine在执行类似以下代码的操作:

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

还有一个goroutine在执行以下操作:

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

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

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

我知道一般来说切片并不是“线程安全”的,但问题更多地是关于slice1 := slice2到底复制了多少内容。

英文:

Let's say I have many goroutines doing something like this:

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

And one doing this:

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

If data := o.data means the internal structure of the slice is copied, this looks like it could be safe, because I'm never modifying anything in the accessible range of the copy. I'm either setting one element outside of the range and increasing the length, or allocating a completely new pointer, but the reader would be operating on the original one.

Are my assumptions correct and this is safe to do?

I'm aware that slices are not meant to be "thread-safe" in general, the question is more about how much does slice1 := slice2 actually copy.

答案1

得分: 8

问题中的代码是不安全的,因为它在一个goroutine中读取一个变量,在另一个goroutine中修改该变量而没有进行同步。

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

type Obj struct {
   mu sync.Mutex // 添加互斥锁
   ... // 其他字段与之前相同
}

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可见的后备数组。

英文:

The code in the question is unsafe because it reads a variable in one goroutine and modifies the variable in another goroutine without synchronization.

Here's one way to make the code safe:

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

It's safe for Reader to range over the local slice variable data because the Writer does not modify the local variable data or the backing array visible through the local variable data.

答案2

得分: 3

有点晚了,但如果你的使用场景是频繁读取和不频繁写入,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(data, 1234)
    o.data.Store(data)
    o.mu.Unlock()
}

这通常比使用 MutexRWMutex 更快。

请注意,这只适用于实际上是副本的数据,因为在这种情况下,当追加时可以安全地保持对先前切片的引用,因为 append() 如果扩展了切片会创建一个新的副本。如果你修改切片的元素或使用其他数据结构,这种方法是不安全的。

英文:

A bit late to the party, but if your use-case is frequent reads and infrequent writes, atomic.Value is designed to solve this:

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

This will generally be much faster than either a Mutex or an RWMutex.

Note that this will only work with data this is effectively a copy, which it is in this case because you can safely maintain a reference to the previous slice when appending, as append() creates a new copy if it extends. If you're mutating the elements of the slice, or using another data structure, this approach is not safe.

huangapple
  • 本文由 发表于 2016年10月28日 05:38:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/40294604.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定