面试题答案
一键面试使用defer与匿名函数实现高并发文件读写及资源释放和性能优化
- 文件读取
在这个函数中,func readFile(filePath string) ([]byte, error) { file, err := os.Open(filePath) if err!= nil { return nil, err } defer func() { if err := file.Close(); err!= nil { log.Println("Error closing file:", err) } }() var result []byte buf := make([]byte, 1024) for { n, err := file.Read(buf) if err!= nil && err!= io.EOF { return nil, err } if n == 0 { break } result = append(result, buf[:n]...) } return result, nil }
os.Open
打开文件后,使用defer
和匿名函数来确保文件在函数结束时关闭。即使读取过程中出现错误,文件也能正确关闭。 - 文件写入
这里func writeFile(filePath string, data []byte) error { file, err := os.Create(filePath) if err!= nil { return err } defer func() { if err := file.Close(); err!= nil { log.Println("Error closing file:", err) } }() _, err = file.Write(data) return err }
os.Create
创建文件后,同样利用defer
和匿名函数在函数结束时关闭文件,保证资源释放。 - 性能优化
- 缓冲区使用:在读取文件时,通过设置合适大小的缓冲区(如
buf := make([]byte, 1024)
),减少系统调用次数,提高I/O性能。 - 并发控制:可以使用
sync.WaitGroup
来管理并发的文件操作,避免资源竞争。例如:
var wg sync.WaitGroup var filePath1 = "file1.txt" var filePath2 = "file2.txt" wg.Add(2) go func() { defer wg.Done() _, err := readFile(filePath1) if err!= nil { log.Println("Error reading file1:", err) } }() go func() { defer wg.Done() err := writeFile(filePath2, []byte("Some data")) if err!= nil { log.Println("Error writing file2:", err) } }() wg.Wait()
- 缓冲区使用:在读取文件时,通过设置合适大小的缓冲区(如
可能遇到的陷阱及解决方案
- 资源竞争
- 陷阱:在高并发环境下,如果多个goroutine同时读写同一个文件,可能会导致数据不一致或文件损坏。
- 解决方案:使用文件锁机制,如
syscall.Flock
。
func writeFileWithLock(filePath string, data []byte) error { file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644) if err!= nil { return err } defer func() { if err := file.Close(); err!= nil { log.Println("Error closing file:", err) } }() if err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX); err!= nil { return err } defer func() { if err := syscall.Flock(int(file.Fd()), syscall.LOCK_UN); err!= nil { log.Println("Error unlocking file:", err) } }() _, err = file.Write(data) return err }
- defer执行顺序
- 陷阱:多个
defer
语句的执行顺序是后进先出(LIFO),如果逻辑复杂,可能会导致意外结果。 - 解决方案:编写
defer
语句时要清晰考虑执行顺序,尽量保持简单逻辑。如果需要复杂的资源释放逻辑,可以封装成独立的函数。
- 陷阱:多个
- 匿名函数闭包变量问题
- 陷阱:匿名函数中使用的闭包变量如果在外部被修改,可能会导致不可预期的行为。
- 解决方案:在需要时,将闭包变量作为参数传递给匿名函数,确保每次调用匿名函数时使用的是特定的值。例如:
这里通过func main() { var filePaths = []string{"file1.txt", "file2.txt"} for _, filePath := range filePaths { filePathCopy := filePath go func() { _, err := readFile(filePathCopy) if err!= nil { log.Println("Error reading file:", err) } }() } time.Sleep(time.Second) }
filePathCopy := filePath
创建了一个新的变量,避免了闭包变量在循环过程中被修改带来的问题。