英文:
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()
}
这通常比使用 Mutex
或 RWMutex
更快。
请注意,这只适用于实际上是副本的数据,因为在这种情况下,当追加时可以安全地保持对先前切片的引用,因为 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论