Go 示例: 互斥锁

在前面的示例中,我们看到了如何使用 原子操作 来管理简单的计数器状态。对于更复杂的状态,我们可以使用 互斥锁 来安全地跨多个 goroutine 访问数据。

package main
import (
    "fmt"
    "sync"
)

容器包含一个计数器映射;由于我们希望从多个 goroutine 并发更新它,因此我们添加了一个 Mutex 来同步访问。请注意,互斥锁不能被复制,因此如果此 struct 被传递,则应通过指针进行传递。

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

在访问 counters 之前锁定互斥锁;使用 defer 语句在函数结束时解锁它。

func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

请注意,互斥锁的零值可以直接使用,因此这里不需要初始化。

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup

此函数在循环中递增一个命名计数器。

    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

并发运行多个 goroutine;请注意,它们都访问同一个 Container,其中两个访问同一个计数器。

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

等待 goroutine 完成

    wg.Wait()
    fmt.Println(c.counters)
}

运行程序表明计数器按预期更新。

$ go run mutexes.go
map[a:20000 b:10000]

接下来,我们将看看如何仅使用 goroutine 和通道来实现相同的状态管理任务。

下一个示例:有状态的 Goroutine.