goroutine lock
package main
import (
"fmt"
"sync"
)
type Container struct {
mu sync.Mutex
counters map[string]int
}
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()
}
wg.Add(3)
go doIncrement("a", 10000)
go doIncrement("a", 10000)
go doIncrement("b", 10000)
wg.Wait()
fmt.Println(c.counters)
}
是的,你的理解是正确的。
在这段代码中,Container 结构体使用了一个互斥锁 (sync.Mutex) 来确保对 counters 字典的并发访问是安全的。
关键点
-
互斥锁 (
sync.Mutex):- 用于保护共享资源,防止多个 goroutine 同时修改
counters。 c.mu.Lock():在对共享资源进行操作前锁住。defer c.mu.Unlock():操作完成后解锁,defer确保即使函数中途返回也会解锁。
- 用于保护共享资源,防止多个 goroutine 同时修改
-
并发安全:
c.inc(name)方法在修改counters时使用锁,确保同一时刻只有一个 goroutine 能修改counters。- 这避免了竞态条件和数据不一致的问题。
-
使用
sync.WaitGroup:- 用于等待所有 goroutine 完成。
wg.Add(3):表示有三个 goroutine。wg.Done():每个 goroutine 完成后调用,减少计数。wg.Wait():阻塞主 goroutine,直到计数归零。
这种模式确保了即使在高并发环境下,数据操作依然是安全的。
vs chan
是的,Go 的 chan 确实具有同步的特性。
关键点
-
阻塞行为:
- 发送阻塞:当一个 goroutine 向一个未缓冲或已满的通道发送数据时,它会阻塞,直到另一个 goroutine 从该通道接收数据。
- 接收阻塞:当一个 goroutine 从一个空的通道接收数据时,它会阻塞,直到有数据可供接收。
-
同步特性:
- 通道的阻塞行为可以用于在 goroutine 之间传递信号或数据,从而实现同步。
- 通过这种方式,通道可以在没有显式锁的情况下协调多个 goroutine。
-
缓冲通道:
- 缓冲通道可以存储一定数量的元素,在缓冲区未满时,发送操作不会阻塞。
- 当缓冲区满时,发送操作会阻塞,直到有空间可用。
-
无锁同步:
- 使用通道可以避免显式使用锁来同步数据共享,简化了代码的复杂性。
通道是 Go 并发模型的重要组成部分,利用其阻塞特性,可以有效地实现 goroutine 之间的通信和同步。