Go语言调用Write为何实际write系统调用次数远少于预期?
我正在学习Linux系统调用(syscall)的概念,为此编写了一段Go语言程序,循环调用Write方法1000次向文件写入数据:
package main import ( "fmt" "os" "time" "math/rand" ) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func RandStringBytes(n int) []byte { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return b } func main(){ stat, err := os.Stat(os.Args[1]) fmt.Println(stat, err) time.Sleep(time.Second) file, _ := os.OpenFile(os.Args[1], os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) for i := 0; i <= 1000; i++{ a := RandStringBytes(1000) file.Write(a) } file.Close() }
原本预期会触发1000次write系统调用,但通过strace ./main file.txt 2>&1 | grep 'write' | wc -l统计时,得到的write系统调用次数却随机波动,有时约200次,有时约300次,远少于预期次数。怀疑过写入字符串的随机性是否会影响,但仍不明白为何系统调用次数与预期不符,也不确定是否是对strace工具的理解有误导致统计遗漏,想请教出现这种情况的原因。
Go标准库的用户态缓冲机制:Go的
os.File底层默认封装了用户态缓冲区(基于bufio实现),并非每次调用File.Write都会直接触发write系统调用。调用Write时,数据会先写入用户态内存缓冲区,只有当缓冲区被填满、手动调用Flush或者关闭文件时,才会一次性将缓冲数据通过write系统调用写入内核。这是实际系统调用次数远少于1000次的核心原因。次数波动的由来:写入内容的随机性不会影响缓冲逻辑,但Go的goroutine调度时机、缓冲区填充的微小差异,会导致每次程序运行时缓冲区被填满的次数略有不同,因此最终统计的
write系统调用次数会出现小幅波动。strace统计无遗漏:你的
strace使用方式是正确的,并没有遗漏系统调用。问题出在Go标准库的上层封装,而非工具使用错误。
验证方法
如果想让每次Write都触发一次write系统调用,可以禁用用户态缓冲:
- 手动使用
bufio.NewWriter并在每次Write后调用Flush:
writer := bufio.NewWriter(file) for i := 0; i <= 1000; i++{ a := RandStringBytes(1000) writer.Write(a) writer.Flush() // 强制将缓冲数据写入内核,触发系统调用 }
- 直接调用底层的
syscall.Write(不推荐,会绕过标准库的安全封装)。
内容的提问来源于stack exchange,提问作者Ilya Boltnev

