Golang中文件被占用无法访问的最佳实践

Golang中文件被占用无法访问的最佳实践 你好,

我是Go语言和编程的新手。我创建了一个应用程序,每当不同的函数执行某些操作时(例如,将文件从一个文件夹复制到其他文件夹,或从文件夹中删除文件),它会在本地将日志信息存储到一个text.log文件中。这工作正常,但我很快遇到了一个问题:如果text.log文件被另一个程序锁定使用(例如,同事用Excel打开查看),应用程序就会崩溃。

我非常希望日志信息能够被存储到text.log文件中,而不是等待其他程序或同事关闭文件(因为这可能很容易花费数小时甚至数天)。

使用以下示例,如果text.log被另一个程序打开并锁定,作为最佳实践,我应该怎么做才能避免崩溃?

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.OpenFile("text.log",
		os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Println(err)
	}
	defer f.Close()
	if _, err := f.WriteString("text to append\n"); err != nil {
		log.Println(err)
	}
}

谢谢。


更多关于Golang中文件被占用无法访问的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

我认为问题在于你仅以写入模式打开文件,因此如果文件被另一个进程锁定,你的程序将会崩溃。 也许使用 O_RDWR 可以解决这个问题,不过我认为你需要在锁定进程中关闭文件…

更多关于Golang中文件被占用无法访问的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理文件被锁定的情况,最佳实践是使用非阻塞的文件打开方式并实现重试机制。以下是改进后的代码示例:

package main

import (
	"log"
	"os"
	"time"
)

func writeLogWithRetry(filename, content string, maxRetries int) error {
	var f *os.File
	var err error
	
	for i := 0; i < maxRetries; i++ {
		// 使用O_RDWR而不是O_WRONLY,并移除O_APPEND标志
		f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644)
		if err != nil {
			if os.IsPermission(err) {
				// 文件被锁定,等待后重试
				time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
				continue
			}
			return err
		}
		defer f.Close()
		
		// 移动到文件末尾
		_, err = f.Seek(0, 2)
		if err != nil {
			f.Close()
			return err
		}
		
		// 写入内容
		_, err = f.WriteString(content)
		if err != nil {
			f.Close()
			// 如果是权限错误,可能是写入过程中文件被锁定
			if os.IsPermission(err) && i < maxRetries-1 {
				time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
				continue
			}
			return err
		}
		
		// 成功写入
		return nil
	}
	
	return err
}

func main() {
	err := writeLogWithRetry("text.log", "text to append\n", 3)
	if err != nil {
		log.Printf("Failed to write log after retries: %v\n", err)
		
		// 备选方案:写入临时文件
		tempFile := "text.log.tmp"
		if f, err := os.OpenFile(tempFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
			defer f.Close()
			f.WriteString("text to append\n")
			log.Printf("Log written to temporary file: %s\n", tempFile)
		}
	}
}

更健壮的实现可以使用文件锁和更复杂的重试策略:

package main

import (
	"log"
	"os"
	"sync"
	"time"
)

type Logger struct {
	filename string
	mu       sync.Mutex
}

func NewLogger(filename string) *Logger {
	return &Logger{filename: filename}
}

func (l *Logger) WriteLog(content string) error {
	l.mu.Lock()
	defer l.mu.Unlock()
	
	const maxRetries = 5
	const baseDelay = 100 * time.Millisecond
	
	for retry := 0; retry < maxRetries; retry++ {
		// 尝试以独占模式打开文件
		file, err := os.OpenFile(l.filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			if os.IsPermission(err) && retry < maxRetries-1 {
				delay := baseDelay * time.Duration(1<<uint(retry)) // 指数退避
				time.Sleep(delay)
				continue
			}
			return err
		}
		
		// 尝试获取文件锁(非阻塞)
		err = lockFile(file)
		if err != nil {
			file.Close()
			if retry < maxRetries-1 {
				delay := baseDelay * time.Duration(1<<uint(retry))
				time.Sleep(delay)
				continue
			}
			return err
		}
		
		// 写入日志
		_, err = file.WriteString(content)
		unlockFile(file)
		file.Close()
		
		if err != nil {
			return err
		}
		
		return nil
	}
	
	return os.ErrPermission
}

// 平台相关的文件锁实现
func lockFile(f *os.File) error {
	// Windows系统使用LockFileEx
	// Linux/Mac使用flock或fcntl
	// 这里简化处理,实际需要根据平台实现
	return nil
}

func unlockFile(f *os.File) error {
	return nil
}

func main() {
	logger := NewLogger("text.log")
	
	if err := logger.WriteLog("text to append\n"); err != nil {
		log.Printf("Error writing log: %v\n", err)
		
		// 写入系统临时文件作为最后手段
		if tmpFile, err := os.CreateTemp("", "log_*.tmp"); err == nil {
			defer os.Remove(tmpFile.Name())
			tmpFile.WriteString("text to append\n")
			log.Printf("Log saved to temporary file: %s\n", tmpFile.Name())
		}
	}
}

对于生产环境,建议使用成熟的日志库如logruszap,它们已经内置了文件锁和并发写入的处理机制。

回到顶部