使用互斥锁进行并发数据访问

GolangGolangBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本实验旨在演示如何使用互斥锁在多个 goroutine 之间安全地访问数据。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("Golang")) -.-> go/ConcurrencyGroup(["Concurrency"]) go/ConcurrencyGroup -.-> go/mutexes("Mutexes") subgraph Lab Skills go/mutexes -.-> lab-15490{{"使用互斥锁进行并发数据访问"}} end

互斥锁

本实验要解决的问题是,使用多个 goroutine 在循环中递增一个命名计数器,并确保对计数器的访问是同步的。

  • 使用一个 Container 结构体来保存计数器的映射。
  • 使用一个 Mutex 来同步对 counters 映射的访问。
  • Container 结构体应该有一个 inc 方法,该方法接受一个 name 字符串,并递增 counters 映射中相应的计数器。
  • inc 方法在访问 counters 映射之前应该锁定互斥锁,并在函数结束时使用 defer 语句解锁。
  • 使用 sync.WaitGroup 结构体来等待 goroutine 完成。
  • 使用 fmt.Println 函数来打印 counters 映射。
## 运行程序会显示计数器
## 按预期更新。
$ go run mutexes.go
map[a:20000 b:10000]

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

以下是完整代码:

// 在前面的示例中,我们看到了如何使用 [原子操作](atomic-counters) 来管理简单的
// 计数器状态。对于更复杂的状态,我们可以使用 [_互斥锁_](https://en.wikipedia.org/wiki/Mutual_exclusion)
// 来在多个 goroutine 之间安全地访问数据。

package main

import (
	"fmt"
	"sync"
)

// Container 保存一个计数器的映射;由于我们希望
// 从多个 goroutine 并发更新它,所以我们
// 添加一个 `Mutex` 来同步访问。
// 请注意,互斥锁不能被复制,所以如果这个
// `struct` 被传递,应该通过指针来传递。
type Container struct {
	mu       sync.Mutex
	counters map[string]int
}

func (c *Container) inc(name string) {
	// 在访问 `counters` 之前锁定互斥锁;在函数结束时使用 [defer](defer)
	// 语句解锁。
	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)
}

总结

在本实验中,我们学习了如何使用互斥锁在多个 goroutine 之间安全地访问数据。我们创建了一个 Container 结构体来保存计数器的映射,并使用一个 Mutex 来同步对 counters 映射的访问。我们还实现了一个 inc 方法来递增命名计数器,并使用 sync.WaitGroup 结构体来等待 goroutine 完成。最后,我们使用 fmt.Println 函数打印了 counters 映射。