【问题标题】:Why isn't this code hitting a race condition?为什么这段代码没有达到竞争条件?
【发布时间】:2019-05-08 10:28:09
【问题描述】:

我有这个 go 代码,它遍历目录文件树并生成其中每个文件的 MD5 哈希,并将结果写入输出文件。

package main

import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "io"
    "os"
    "path/filepath"
    "sync"
)

func main() {

    filePath := os.Args[1]
    output := os.Args[2]
    wg := &sync.WaitGroup{}

    err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
        if !info.IsDir() {
            wg.Add(1)
            go func(path string) {
                md5Sum, _ := md5File(path)
                if err := writeToFile(path, md5Sum, output); err != nil {
                    panic(err)
                }
                wg.Done()
            }(path)
        }
        return nil
    })
    if err != nil {
        panic(err)
    }
    wg.Wait()
}

func md5File(filePath string) (string, error) {
    file, err := os.Open(filePath)
    if err != nil {
        return "", err
    }
    defer file.Close()

    hash := md5.New()
    if _, err := io.Copy(hash, file); err != nil {
        return "", err
    }

    checksum := hash.Sum(nil)

    return string(hex.EncodeToString(checksum)), nil
}

func writeToFile(filePath, md5sum, output string) error {

    file, err := os.OpenFile(output, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
    if err != nil {
        return err
    }

    defer file.Close()

    file.WriteString(fmt.Sprintf("%s %s\n", md5sum, filePath))

    return file.Sync()
}

据我了解,有时在写入输出文件时一定会遇到竞争条件,但它永远不会。我的意思是我已经无数次地执行了这段代码,从来没有遇到任何问题。它甚至每次都产生相同的结果。

这是为什么呢?我错过了什么吗?

更新:当我说它必然会面临竞争条件时,我的意思是当运行多个 goroutine 时,可能有多个 goroutine 想要同时写入文件。 p>

【问题讨论】:

  • “根据我的理解,它一定会在某个时候遇到竞争条件......” - 您能否更详细地解释您的理解,即您在哪里看到比赛条件应该在什么情况下发生,如果比赛发生,结果应该如何?到目前为止,您只是期待一场比赛而不告诉任何细节,然后期待其他人解释为什么您没有解释的事情没有发生。
  • @SteffenUllrich 我已经为问题添加了更多细节。
  • 必须注意:并非所有文件系统都与 POSIX 兼容。最好是同步/序列化对文件的写入(附加)。每次都打开/关闭它是一种浪费。打开它一次,然后在你处理完所有文件的地方关闭它。
  • @icza 我明白了。我故意想强制一个竞争条件只是为了测试。

标签: multithreading go race-condition goroutine


【解决方案1】:

当我说它必然会面临竞争条件时,我的意思是当运行多个 goroutine 时,可能有多个 goroutine 想要同时写入文件。

多次打开一个文件不是问题。并且由于您明确使用O_APPEND,因此写入之间的交互不会很糟糕。引用 来自man open

O_APPEND ...
在每次 write(2) 之前,文件偏移量位于文件末尾,就像使用 lseek(2) 一样。 文件偏移量的修改和写入操作作为单个原子步骤执行

【讨论】:

  • 好吧,删除 O_APPEND 标志没有任何区别。也许会慢一点,但结果是一样的。
  • @HamedMomeni:如果你删除O_APPEND,你会得到一个总是只包含一个md5sum的文件,因为每个新的open+write都会写在文件的开头。也许你应该更清楚你对比赛结果的确切期望。
  • 好吧,我希望有些行会被混合,而且总行数应该是不一致的。但这段代码并非如此。
  • 至于为什么我没有得到单行输出,我将文件打开移到了写入函数之外,以免每次调用都重新打开文件。
  • @HamedMomeni:“我将文件打开移到了写入函数之外,以免每次调用都重新打开文件。” - 这与你的完全不同在你的问题中做了。因为在这种情况下,您在操作系统内核中拥有共享文件偏移量 - 这又不会产生竞争。如果你在 go 例程中打开,寻找文件末尾然后写入,可能会发生竞争。因为只有这样你才有一个本地文件偏移量,它会受到寻找和写入之间的竞争的影响。
猜你喜欢
  • 1970-01-01
  • 2019-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多