You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go中判断文件是否被其他程序打开,避免误归档删除?

在Go中可靠检查文件是否被另一个程序打开的方案

问题场景

我用Go开发程序A,需要归档目录下的日志文件,但要排除被程序B正在打开写入的日志文件——程序B会在24小时后关闭这些文件,之后才能归档,两个程序完全独立运行。

当前实现没做文件是否被打开的检查,导致归档了正在被程序B写入的文件;现有逻辑是把文件复制到归档包后删除原文件。我试过用os.Open和os.OpenFile来检查,但都没成功,想知道在归档前如何可靠判断文件是否被另一个Go程序打开?

附现有归档核心代码:

func zipFile(filename string) error {
    file, err := os.Open(filename)

    if err != nil {
        return err
    }

    defer func(file *os.File) {
        err = file.Close()

        if err != nil {
            fmt.Println(err)
        }
    }(file)

    fileInfo, err := file.Stat()

    if err != nil {
        return err
    }

    archiveFile, err := os.Create(fmt.Sprintf("%s.zip", filename))

    if err != nil {
        return err
    }

    defer func(archiveFile *os.File) {
        err = archiveFile.Close()

        if err != nil {
            fmt.Println(err)
        }
    }(archiveFile)

    zipWriter := zip.NewWriter(archiveFile)

    if err != nil {
        return err
    }

    defer func(zipWriter *zip.Writer) {
        err = zipWriter.Close()

        if err != nil {
            fmt.Println(err)
        }
    }(zipWriter)

    writer, err := zipWriter.Create(fileInfo.Name())

    if err != nil {
        return err
    }

    _, err = io.Copy(writer, file)

    if err != nil {
        return err
    }

    err = os.Remove(filename)

    if err != nil {
        return err
    }

    fmt.Println(fmt.Sprintf("file %s was zipped", fileInfo.Name()))

    return nil
}

可行解决方案

跨进程文件状态检查没有统一的跨平台标准,推荐结合操作系统特性或设计层面的优化来处理:

1. 利用文件独占锁(推荐跨平台方案)

让程序B在打开日志文件时加独占写锁,程序A通过尝试获取锁来判断文件是否被占用:

  • 程序B侧:打开文件时添加独占锁,阻止其他进程的写操作或独占锁请求
    // 程序B打开日志文件的示例
    file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        return err
    }
    // Unix平台加独占锁,Windows需用LockFileEx替代
    err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX)
    if err != nil {
        file.Close()
        return err
    }
    // 程序退出或关闭文件时解锁
    defer syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
    defer file.Close()
    
  • 程序A侧:尝试获取共享锁,失败则说明文件被独占占用
    func isFileInUse(filename string) bool {
        file, err := os.OpenFile(filename, os.O_RDWR, 0644)
        if err != nil {
            return true
        }
        defer file.Close()
        // 非阻塞尝试加共享锁,失败则文件被独占
        err = syscall.Flock(int(file.Fd()), syscall.LOCK_SH|syscall.LOCK_NB)
        if err != nil {
            return true
        }
        // 解锁
        syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
        return false
    }
    
    嫌平台判断麻烦的话,可以用github.com/gofrs/flock这类跨平台锁库简化实现。

2. 基于时间或命名规则(简单易实现)

既然程序B会24小时后关闭文件,完全可以跳过检查逻辑,直接通过规则筛选可归档文件:

  • 时间筛选:只归档最后修改时间超过24小时的文件
    func isEligibleForArchive(filename string) (bool, error) {
        fi, err := os.Stat(filename)
        if err != nil {
            return false, err
        }
        cutoff := time.Now().Add(-24 * time.Hour)
        return fi.ModTime().Before(cutoff), nil
    }
    
  • 命名规则:程序B将正在写入的日志命名为xxx.log.active,关闭后重命名为xxx.log,程序A只归档不带.active后缀的文件。

3. 检查进程打开文件(仅应急方案)

依赖操作系统特性,但稳定性和兼容性较差,不推荐作为核心逻辑:

  • Unix/Linux:解析/proc/<pid>/fd目录或调用lsof命令,但需要进程权限,且可能出现竞态
  • Windows:调用CreateFile并指定非共享参数,失败则说明文件被独占打开,但可能误判只读打开的情况

归档逻辑兜底优化

不管用哪种检查方式,都可能出现检查后文件被程序B打开的竞态问题,建议修改归档删除步骤:
不要直接删除原文件,先原子性重命名为临时文件,再归档临时文件,最后删除临时文件。重命名操作不会影响程序B的写入(程序B会继续写原文件名的新文件):

// 替换原代码中的os.Remove(filename)
tmpName := filename + ".tmp"
err = os.Rename(filename, tmpName)
if err != nil {
    return err
}
// 归档完成后删除临时文件,失败可记录日志后续重试
err = os.Remove(tmpName)
if err != nil {
    fmt.Printf("failed to remove tmp file %s: %v\n", tmpName, err)
}

内容的提问来源于stack exchange,提问作者Daniel Matute

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:47:06