Golang作为一门原生支持并发的编程语言,在多线程场景下处理文件操作时,需要特别注意并发安全的问题,否则很容易出现数据写入错乱、文件内容丢失等异常情况。下面我们先看一个基础的文件写入示例,再逐步分析多线程场景下的处理方式。

Golang基础文件操作回顾
在单线程场景下,Golang操作文件主要使用os包提供的相关方法,常见的写入操作示例如下:
package main
import (
"os"
)
func main() {
// 打开文件,如果不存在则创建,权限为0644
file, err := os.OpenFile("test.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer file.Close()
// 写入内容
_, err = file.WriteString("单线程写入内容n")
if err != nil {
panic(err)
}
}
多线程并发读写文件的问题
当多个goroutine同时操作同一个文件时,如果没有做并发控制,就会出现问题。比如我们启动3个goroutine同时向同一个文件写入内容:
package main
import (
"os"
"time"
)
func writeFile(file *os.File, content string) {
_, err := file.WriteString(content + "n")
if err != nil {
panic(err)
}
}
func main() {
file, err := os.OpenFile("test.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer file.Close()
// 启动3个goroutine并发写入
for i := 0; i < 3; i++ {
go writeFile(file, "并发写入内容"+string(rune('A'+i)))
}
time.Sleep(time.Second)
}
运行上述代码后,打开test.txt文件,可能会看到内容交错的情况,比如并发写入内容An并发写入内容Bn并发写入内容Cn的写入顺序被打乱,甚至出现部分内容丢失,这就是并发读写没有加锁导致的典型问题。
Golang锁机制解决并发文件操作问题
互斥锁sync.Mutex
互斥锁是最基础的并发控制工具,同一时间只允许一个goroutine持有锁,其他goroutine需要等待锁释放才能获取。我们可以用互斥锁保护文件写入操作:
package main
import (
"os"
"sync"
"time"
)
var mu sync.Mutex
func writeFileWithMutex(file *os.File, content string) {
// 加锁,保证同一时间只有一个goroutine执行写入
mu.Lock()
defer mu.Unlock()
_, err := file.WriteString(content + "n")
if err != nil {
panic(err)
}
}
func main() {
file, err := os.OpenFile("test.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer file.Close()
// 启动3个goroutine并发写入
for i := 0; i < 3; i++ {
go writeFileWithMutex(file, "互斥锁写入内容"+string(rune('A'+i)))
}
time.Sleep(time.Second)
}
使用互斥锁后,所有写入操作会串行执行,不会出现内容交错的问题,适合写多读少的场景。
读写锁sync.RWMutex
如果场景中读操作远多于写操作,使用互斥锁会导致读操作也被串行化,效率较低。这时候可以使用读写锁,读写锁的规则是:读锁可以多个goroutine同时持有,写锁只能单独持有,且读锁和写锁互斥。
package main
import (
"bufio"
"os"
"sync"
"time"
)
var rwMu sync.RWMutex
// 读文件操作,加读锁
func readFile(file *os.File) {
rwMu.RLock()
defer rwMu.RUnlock()
// 将文件指针移到开头
file.Seek(0, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// 这里可以处理读取到的内容
}
}
// 写文件操作,加写锁
func writeFileWithRWMutex(file *os.File, content string) {
rwMu.Lock()
defer rwMu.Unlock()
_, err := file.WriteString(content + "n")
if err != nil {
panic(err)
}
}
func main() {
file, err := os.OpenFile("test.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer file.Close()
// 启动多个读goroutine和写goroutine
for i := 0; i < 2; i++ {
go writeFileWithRWMutex(file, "读写锁写入内容"+string(rune('A'+i)))
}
for i := 0; i < 5; i++ {
go readFile(file)
}
time.Sleep(time.Second)
}
不同锁机制的选择建议
我们可以通过下面的表格对比两种锁的适用场景:
| 锁类型 | 并发特性 | 适用场景 |
|---|---|---|
| sync.Mutex互斥锁 | 同一时间只有一个goroutine能持有锁,读写操作都互斥 | 写操作频繁,或者读写操作比例接近的场景 |
| sync.RWMutex读写锁 | 多个读goroutine可以同时持有读锁,写锁单独持有,读写互斥 | 读操作远多于写操作的场景 |
注意事项
- 锁的作用范围要尽可能小,只保护需要并发控制的文件操作代码,避免锁持有时间过长影响性能。
- 如果文件操作涉及多个文件的联动修改,需要保证所有相关操作都在同一个锁的保护范围内,避免出现部分修改完成部分未完成的情况。
- 使用
defer释放锁时,要注意函数执行流程,避免锁提前释放或者忘记释放。
需要注意的是,Golang的文件操作本身不是并发安全的,所有多线程场景下的文件操作都必须通过锁机制或者其他并发控制手段保证安全,不能依赖系统层面的文件锁,因为不同操作系统的文件锁实现存在差异,可移植性较差。
Golang文件操作并发读写锁机制sync_Mutex修改时间:2026-07-06 22:39:31