Skip to main content

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 字典的并发访问是安全的。

关键点

  1. 互斥锁 (sync.Mutex)

    • 用于保护共享资源,防止多个 goroutine 同时修改 counters
    • c.mu.Lock():在对共享资源进行操作前锁住。
    • defer c.mu.Unlock():操作完成后解锁,defer 确保即使函数中途返回也会解锁。
  2. 并发安全

    • c.inc(name) 方法在修改 counters 时使用锁,确保同一时刻只有一个 goroutine 能修改 counters
    • 这避免了竞态条件和数据不一致的问题。
  3. 使用 sync.WaitGroup

    • 用于等待所有 goroutine 完成。
    • wg.Add(3):表示有三个 goroutine。
    • wg.Done():每个 goroutine 完成后调用,减少计数。
    • wg.Wait():阻塞主 goroutine,直到计数归零。

这种模式确保了即使在高并发环境下,数据操作依然是安全的。

vs chan

是的,Go 的 chan 确实具有同步的特性。

关键点

  1. 阻塞行为

    • 发送阻塞:当一个 goroutine 向一个未缓冲或已满的通道发送数据时,它会阻塞,直到另一个 goroutine 从该通道接收数据。
    • 接收阻塞:当一个 goroutine 从一个空的通道接收数据时,它会阻塞,直到有数据可供接收。
  2. 同步特性

    • 通道的阻塞行为可以用于在 goroutine 之间传递信号或数据,从而实现同步。
    • 通过这种方式,通道可以在没有显式锁的情况下协调多个 goroutine。
  3. 缓冲通道

    • 缓冲通道可以存储一定数量的元素,在缓冲区未满时,发送操作不会阻塞。
    • 当缓冲区满时,发送操作会阻塞,直到有空间可用。
  4. 无锁同步

    • 使用通道可以避免显式使用锁来同步数据共享,简化了代码的复杂性。

通道是 Go 并发模型的重要组成部分,利用其阻塞特性,可以有效地实现 goroutine 之间的通信和同步。