
本文介绍使用 go 语言的 sync.rwmutex 实现多 goroutine 下对同一文件的安全读写,确保读操作不阻塞写操作、写操作不破坏读一致性,避免竞态与数据损坏。
本文介绍使用 go 语言的 sync.rwmutex 实现多 goroutine 下对同一文件的安全读写,确保读操作不阻塞写操作、写操作不破坏读一致性,避免竞态与数据损坏。
在高并发 Web 服务中,常需将请求日志或状态数据持续追加写入单个文件(如 access.log 或 metrics.json),同时支持后台任务(如监控采集、离线分析)定时读取该文件内容。此时若无同步机制,直接并发读写会导致数据错乱、读取截断或 panic。Go 标准库提供的 sync.RWMutex 是解决该问题的经典模式:它允许多个读操作并行执行,但写操作独占临界区,兼顾性能与安全性。
核心实现逻辑
- 读操作:调用 RWMutex.RLock() 获取读锁,读取完成后立即 RUnlock() —— 多个读协程可同时进行,互不阻塞;
- 写操作:调用 RWMutex.Lock() 获取写锁,写入完成后 Unlock() —— 写操作期间所有新读/写请求均等待,确保写入原子性;
- 关键原则:锁仅保护「文件 I/O 操作本身」,而非文件内容语义;因此需确保写入是追加(os.O_APPEND)或覆盖时完整重写(如先写临时文件再原子 rename),避免部分写导致读取到脏数据。
完整示例代码
package main
import (
"fmt"
"os"
"sync"
"time"
)
var fileMutex = new(sync.RWMutex)
const fileName = "data.log"
// 安全读取文件全部内容
func readFile() ([]byte, error) {
fileMutex.RLock()
defer fileMutex.RUnlock()
data, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
return data, nil
}
// 安全追加写入一行日志
func appendToFile(line string) error {
fileMutex.Lock()
defer fileMutex.Unlock()
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to open file for append: %w", err)
}
defer f.Close()
_, err = f.WriteString(fmt.Sprintf("%s\n", line))
return err
}
// 模拟并发场景
func main() {
// 启动写入 goroutine
go func() {
for i := 0; i < 10; i++ {
appendToFile(fmt.Sprintf("event-%d", i))
time.Sleep(100 * time.Millisecond)
}
}()
// 启动读取 goroutine
go func() {
for i := 0; i < 5; i++ {
data, err := readFile()
if err != nil {
fmt.Printf("Read error: %v\n", err)
} else {
fmt.Printf("Read %d bytes: %s", len(data), string(data))
}
time.Sleep(300 * time.Millisecond)
}
}()
time.Sleep(2 * time.Second)
}注意事项与最佳实践
- ✅ 锁粒度要准:RLock()/Lock() 仅包裹实际的 os.ReadFile 或 os.OpenFile 等 I/O 调用,避免在锁内做耗时解析或网络请求;
- ⚠️ 避免死锁:确保 defer Unlock() 总在 Lock()/RLock() 之后,且不跨函数边界传递未解锁的 mutex;
- ? 跨进程不适用:sync.RWMutex 仅作用于同一进程内 goroutine,若存在多个进程同时读写该文件(如不同服务实例),需改用文件系统级锁(如 flock)或外部协调服务(如 etcd);
- ? 大文件优化建议:对超大日志文件,优先采用 bufio.Scanner 分行读取 + RWMutex 控制打开/关闭,而非一次性 ReadFile 加载全部内容;
- ? 更健壮方案:生产环境推荐结合日志轮转(log rotation)与 tail -f 类机制(如 fsnotify 监听文件变更),将读写解耦为「写入当前文件 + 读取已冻结历史文件」。
通过合理运用 sync.RWMutex,你能在保证数据一致性的前提下,高效支撑高频写入与低频读取共存的典型文件访问场景。


















