假设我有很多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
实际复制了多少。
答案 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()
}
这通常比Mutex
或RWMutex
快得多。
请注意,这仅适用于实际上是副本的数据,在这种情况下是这样,因为附加时您可以安全地维护对上一个切片的引用,因为append()
会在扩展时创建新副本。如果您要更改分片的元素或使用其他数据结构,则此方法并不安全。