文章目录

Go语言竞态条件在map并发读写时的崩溃复现

发布于 2026-05-05 19:20:24 · 浏览 15 次 · 评论 0 条

Go语言竞态条件在map并发读写时的崩溃复现

Go 语言内置的 map 类型并不支持并发安全读写。当多个 goroutine 同时对同一个 map 进行写入或读写混合操作时,程序会抛出 fatal error 并崩溃。本指南将直接复现这一经典崩溃场景,并演示如何使用 Go 的竞态检测工具定位问题。


1. 初始化实验环境

打开 终端,执行 以下命令创建一个新的项目目录并初始化 Go Module。

mkdir map_race_demo
cd map_race_demo
go mod init map_race_demo

2. 编写引发崩溃的代码

创建 一个名为 main.go 的文件。

输入 以下代码。该代码启动了两个 goroutine,一个负责不断写入 map,另一个负责不断读取 map,故意制造并发冲突。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 初始化一个 map
    m := make(map[int]int)

    // 启动一个 goroutine 进行并发写入
    go func() {
        for i := 0; i < 10000; i++ {
            m[i] = i // 写入操作
        }
    }()

    // 启动一个 goroutine 进行并发读取
    go func() {
        for i := 0; i < 10000; i++ {
            _ = m[i] // 读取操作
        }
    }()

    // 等待足够的时间让并发冲突发生
    time.Sleep(1 * time.Second)
    fmt.Println("程序执行结束")
}

3. 复现并发崩溃现象

执行 以下命令运行程序。由于竞态条件的不确定性,可能需要运行 多次才能看到崩溃,或者可能第一次运行就会崩溃。

go run main.go

观察 终端输出。如果触发崩溃,你将看到类似如下的错误信息:

fatal error: concurrent map read and map write

goroutine 7 [running]:
...

或者:

fatal error: concurrent map writes

goroutine 6 [running]:
...

上述报错信息明确指出了程序发生了 concurrent map read and map write(并发读写)或 concurrent map writes(并发写)错误。


4. 使用竞态检测器验证

为了在非崩溃状态下也能精准发现问题,Go 提供了 -race 工具。执行 带有竞态检测标志的命令:

go run -race main.go

分析 输出结果。即使程序此时没有崩溃并正常打印了“程序执行结束”,终端中也会出现详细的 WARNING: DATA RACE 警告。

==================
WARNING: DATA RACE
Write at 0x00c0000a2008 by goroutine 7:
  main.main.func1()
      /path/to/main.go:14 +0x...
  ...

Previous read at 0x00c0000a2008 by goroutine 8:
  main.main.func2()
      /path/to/main.go:21 +0x...
  ...
==================

这段信息告诉我们:

  1. Write at...:在第 14 行发生了写入操作(即 m[i] = i)。
  2. Previous read at...:在此之前,第 21 行发生了读取操作(即 _ = m[i])。
  3. 这两个操作由不同的 goroutine (goroutine 7goroutine 8) 在没有同步机制的情况下同时访问了同一个内存地址。

以下流程图直观展示了这一竞态过程:

graph TD A["Main Start"] --> B["Launch Writer Goroutine"] A --> C["Launch Reader Goroutine"] B -->|Write: m[i] = i| D["Shared Map Memory"] C -->|Read: x = m[i]| D D -->|Conflict Detected| E["Runtime Panic / Data Race Warning"]

5. 修复并发安全问题

要解决此问题,必须使用互斥锁 sync.Mutex 来保证同一时间只有一个 goroutine 能访问 map。

修改 main.go 代码如下:

package main

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

func main() {
    // 初始化 map 和互斥锁
    m := make(map[int]int)
    var mu sync.Mutex

    // 启动写入 goroutine
    go func() {
        for i := 0; i < 10000; i++ {
            mu.Lock()         // 加锁
            m[i] = i          // 临界区:安全写入
            mu.Unlock()       // 解锁
        }
    }()

    // 启动读取 goroutine
    go func() {
        for i := 0; i < 10000; i++ {
            mu.Lock()         // 加锁
            _ = m[i]          // 临界区:安全读取
            mu.Unlock()       // 解锁
        }
    }()

    time.Sleep(1 * time.Second)
    fmt.Println("程序执行结束,安全退出")
}

再次执行 go run -race main.go

确认 终端不再输出 WARNING: DATA RACE,且程序每次都能稳定运行结束。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文