在 Go 中创建一个新的 Mutex 是线程安全的吗?

Is it thread safe to create a new Mutex in Go?

我在 Go 中有一个包含互斥锁的结构,我想确保互斥锁永远不会 nil。为此,我实现了一个 GetMutex() 函数,它检查互斥量是否为 nil,如果是,则为其赋值。

我的问题是:下面的代码线程安全吗?如果不是,确保 mux 始终被初始化的惯用方法是什么?我唯一能想到的就是在我的 GetMutex() 函数中使用这个包中的全局互斥锁,但也许有不同的方法。

package main

import (
    "sync"
)

type Counter struct {
    mux *sync.Mutex
    counter int
}

// Is this thread safe?
func (c *Counter) GetMux() *sync.Mutex {
    if c.mux == nil {
        c.mux = &sync.Mutex{}
    }
    return c.mux
}

func (c *Counter) Inc() {
    c.GetMux().Lock()
    c.counter++
    c.GetMux().Unlock()
}

func main() {
    c := &Counter{}
    c.Inc()
}

不,如果同时从多个 goroutine 调用 Counter.GetMux() 是不安全的:GetMux() 读取和写入 Counter.mux 字段。

一般方法是使用类似“构造函数”的函数来负责初始化,如下所示:

func NewCounter() *Counter {
    return &Counter{
        mux: &sync.Mutex{},
    }
}

当然,总是用这个 NewCounter() 来创建计数器。

另一种有限的方式是使用 non-pointer 互斥锁值:

type Counter struct {
    mux     sync.Mutex
    counter int
}

所以当你有一个 Counter 结构值时,它在设计上包含一个互斥体。但是如果你这样做,那么 Counter 应该始终用作指针,并且 Counter 结构值不能被复制(否则互斥字段也会被复制,但是作为 [=21 的包文档=] 指出:“不应复制包含此包中定义的类型的值。”).

这样做的明显优势是 Counterzero value 是一个有效且现成的计数器(您应该针对您的自定义类型),并且不需要构造函数。