Using a mutex within a struct in Go

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

Using a mutex within a struct in Go

问题

我在Essential Go中看到,在结构体中使用互斥锁并不是太直观。引用Mutex Gotchas页面的话:

不要复制互斥锁

sync.Mutex变量的副本起始状态与原始互斥锁相同,但它不是同一个互斥锁。

复制sync.Mutex几乎总是一个错误,例如通过将其传递给另一个函数或将其嵌入到结构体中并复制该结构体。

如果要共享互斥锁变量,请将其作为指针*sync.Mutex传递。

我不太确定我完全理解了写的内容。我在这里查看了这里,但仍然不太清楚。

以Essential Go中的一个Set为例,我应该像这样使用互斥锁吗:

type StringSet struct {
	m  map[string]struct{}
	mu sync.RWMutex
}

还是像这样使用互斥锁的指针?

type StringSet struct {
	m  map[string]struct{}
	mu *sync.RWMutex
}

我尝试了这两种方式,并在Playground中都能正常工作。

// Delete从集合中删除一个字符串
func (s *StringSet) Delete(str string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.m, str)
}

显然会有多个'Set'的实例,因此每个实例都应该有自己的互斥锁。在这种情况下,是使用互斥锁还是互斥锁的指针更好呢?

英文:

I see in Essential Go that using a mutex within a struct is not too straight-forward. To quote from the Mutex Gotchas page:

> Don’t copy mutexes
>
> A copy of sync.Mutex variable starts with the same state as original
> mutex but it is not the same mutex.
>
> It’s almost always a mistake to copy a sync.Mutex e.g. by passing it
> to another function or embedding it in a struct and making a copy of
> that struct.
>
> If you want to share a mutex variable, pass it as a pointer
> *sync.Mutex.

I'm not quite sure I fully understand exactly what's written. I looked here but still wasn't totally clear.

Taking the Essential Go example of a Set, should I be using the mutex like this:

type StringSet struct {
	m map[string]struct{}
	mu            sync.RWMutex
}

or like this?

type StringSet struct {
	m map[string]struct{}
	mu            *sync.RWMutex
}

I tried both with the Delete() function within the example and they both work in the Playground.

// Delete removes a string from the set
func (s *StringSet) Delete(str string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.m, str)
}

There will obviously be several instances of a 'Set', and hence each instance should have its own mutex. In such a case, is it preferable to use the mutex or a pointer to the mutex?

答案1

得分: 5

使用第一种方法(一个普通的互斥锁,而不是互斥锁的指针),并传递一个*StringSet(指向你的结构体的指针),而不是一个普通的StringSet

在你在playground中分享的代码中(这个版本):

  • .Add().Exists().Strings()应该获取锁,
  • 否则你的代码符合go中结构体和互斥锁的常规用法。

"不要复制互斥锁"的陷阱适用于操作普通的StringSet结构体的情况:

var setA StringSet
setA.Add("foo")
setA.Add("bar")

func buggyFunction(s StringSet) {
  ...
}

// 陷阱会发生在这里:
var setB = setA
// 或者这里:
buggyFunction(setA)

在上述两种情况中:你将创建一个完整结构体的副本

因此,例如,setB将操作与setA相同的底层map[string]struct{}映射,但互斥锁不会被共享:调用setA.m.Lock()不会阻止从setB修改映射。

英文:

Use the first method (a plain Mutex, not a pointer to a mutex), and pass around a *StringSet (pointer to your struct), not a plain StringSet.

In the code you shared in your playground (that version) :

  • .Add(), .Exists() and .Strings() should acquire the lock,
  • otherwise your code fits a regular use of structs and mutexes in go.

The "Don't copy mutexes" gotcha would apply if you manipulated plain StringSet structs :

var setA StringSet
setA.Add("foo")
setA.Add("bar")

func buggyFunction(s StringSet) {
  ...
}


// the gotcha would occur here :
var setB = setA
// or here :
buggyFunction(setA)

In both cases above : you would create a copy of the complete struct

so setB, for example, would manipulate the same underlying map[string]struct{} mapping as setA, but the mutex would not be shared : calling setA.m.Lock() wouldn't prevent modifying the mapping from setB.

答案2

得分: -1

如果你选择第一种方式,即:

type StringSet struct {
	m map[string]struct{}
	mu            sync.RWMutex
}

任何意外或有意的结构值赋值/复制都会创建一个新的互斥锁,但底层的映射 m 将保持不变(因为映射本质上是指针)。因此,可以在没有锁定的情况下并发地修改/访问映射。当然,如果你严格遵循“不复制集合”的规则,这种情况就不会发生,但对我来说这没有太多意义。

简而言之:明确选择第二种方式。

英文:

If you go the first way, i.e.

type StringSet struct {
	m map[string]struct{}
	mu            sync.RWMutex
}

any accidental or intentional assignment/copy of a struct value will create a new mutex, but the underlying map m will be the same (because maps are essentially pointers). As a result, it will be possible to modify/access the map concurrently without locking. Of course, if you strictly follow a rule "thou shalt not copy a set", that won't happen, but it doesn't make much sense to me.

TL;DR: definitely the second way.

huangapple
  • 本文由 发表于 2021年8月31日 00:34:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/68987483.html
匿名

发表评论

匿名网友

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

确定