You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言sync.Mutex锁未按预期工作,打印Map对象异常求助

sync.Mutex看似未按预期工作的原因分析

我不确定为什么sync.Mutex未按预期工作,希望能得到相关建议。以下是我的代码:

package main

import (
    "fmt"
    "sync"
    "time"
)

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]++
    
    // fmt.Println("in", name, c.counters) 
    // 这个打印会改变goroutine间的执行表现
    time.Sleep(time.Second)
}

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)
            fmt.Println(name, c.counters)
        }
        wg.Done()
    }

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

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

运行这段代码后,我得到了异常输出:

a map[a:2 b:0]
a map[a:2 b:0]
b map[a:2 b:1]
a map[a:4 b:1]
a map[a:4 b:1]
b map[a:4 b:2]
map[a:4 b:2]

我期望能看到a递增到1、2、3、4的日志,但实际并非如此。当我取消inc函数中的打印语句注释后,就能看到符合预期的日志:

in a map[a:1 b:0]
a map[a:1 b:0]
in a map[a:2 b:0]
a map[a:2 b:0]
in b map[a:2 b:1]
b map[a:2 b:1]
in a map[a:3 b:1]
a map[a:3 b:1]
in a map[a:4 b:1]
a map[a:4 b:1]
in b map[a:4 b:2]
b map[a:4 b:2]
map[a:4 b:2]

问题原因与解决方案

问题核心是你在锁的保护范围外读取了共享资源counters:

  • inc函数里的sync.Mutex只保护了counters的写入操作(c.counters[name]++),但main中fmt.Println(name, c.counters)是无锁的并发读取,不属于临界区操作。
  • 当一个goroutine执行完inc解锁后,另一个goroutine可能立刻抢占锁完成下一次递增,而第一个goroutine此时才执行打印,自然会看到已经被更新后的值。
  • 打开inc内的打印后,打印操作处于锁的保护范围内,输出的是当前临界区的真实状态;同时打印本身的IO阻塞会让goroutine的执行顺序更“同步”,但这只是偶然现象,不是正确的并发处理方式。

正确做法

要保证共享资源的并发安全,所有读写操作都必须处于锁的保护下。可以通过两种方式修复:

  1. 在锁内完成读取和打印
    修改inc函数,把打印逻辑移到锁的范围内:
func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
    fmt.Println(name, c.counters) // 打印操作放入锁保护
    time.Sleep(time.Second)
}

同时移除doIncrement里的打印语句。

  1. 添加带锁的读取方法
    给Container新增一个安全读取counters的方法,返回副本避免外部并发访问原map:
func (c *Container) GetCounters() map[string]int {
    c.mu.Lock()
    defer c.mu.Unlock()
    counterCopy := make(map[string]int, len(c.counters))
    for k, v := range c.counters {
        counterCopy[k] = v
    }
    return counterCopy
}

然后在doIncrement中调用该方法打印:

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

内容的提问来源于stack exchange,提问作者ericobi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 11:45:29