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

Go语言Mutex未正确锁定资源:文件切换后Goroutine读取异常

问题原因与解决方案

问题原因

核心问题出在共享的*os.File是有状态对象:

  • os.File内部维护着文件读取的偏移量,当第一个goroutine调用io.ReadAll读完整个文件后,偏移量会移动到文件末尾。后续goroutine再对同一个*os.File调用ReadAll时,会从末尾开始读取,自然返回空内容。
  • 切换文件的逻辑虽然用了互斥锁保证同步,但替换的只是Root.File指针;在两次文件切换的间隔内,多个goroutine共享同一个*os.File,偏移量被第一个读操作耗尽,导致后续读空。

解决方案

方案1:读取前重置文件偏移量

在每个goroutine读取文件前,先将文件偏移量重置到开头,且该操作要在读锁保护下进行(Seek会修改文件状态,需要同步):

go func(i int, root *Root) {
    root.RLock()
    defer root.RUnlock()
    // 重置文件偏移量到开头
    _, err := root.File.Seek(0, io.SeekStart)
    if err != nil {
        fmt.Printf("go routine %v seek error: %v\n", i, err)
        return
    }
    content, err := io.ReadAll(root.File)
    if err != nil {
        fmt.Printf("go routine %v read error: %v\n", i, err)
        return
    }
    fmt.Printf("go routine %v is reading data %s\n", i, content)
}(i, root)

该方案无需修改文件切换逻辑,仅通过重置偏移量保证每次读取从文件开头开始。

方案2:维护文件名而非文件指针

更稳妥的方式是不在Root中保存*os.File,而是维护当前要读取的文件名。每次读取时独立打开文件、读完关闭,避免共享有状态的文件句柄:

  1. 修改Root结构体:
type Root struct {
    Filename string
    sync.RWMutex
}
  1. 修改startTask的读取逻辑:
go func(i int, root *Root) {
    root.RLock()
    filename := root.Filename
    root.RUnlock()
    // 每次读取独立打开文件
    file, err := os.Open(filename)
    if err != nil {
        fmt.Printf("go routine %v open error: %v\n", i, err)
        return
    }
    defer file.Close()
    content, err := io.ReadAll(file)
    if err != nil {
        fmt.Printf("go routine %v read error: %v\n", i, err)
        return
    }
    fmt.Printf("go routine %v is reading data %s\n", i, content)
}(i, root)
  1. 修改startWorker的文件切换逻辑:
func startWorker() {
    interval := 4 * time.Second
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    f1 := "concurrent/1/f1.txt"
    f2 := "concurrent/2/f2.txt"

    f := f1
    root := &Root{Filename: f}

    go startTask(root)
    for {
        root.Lock()
        if f == f2 {
            f = f1
        } else {
            f = f2
        }
        root.Filename = f
        root.Unlock()
        <-ticker.C
    }
}

该方案彻底避免了共享文件句柄带来的状态干扰,每个goroutine独立操作文件,安全性更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 20:34:55