并发写入文件

Jer*_*via 7 synchronization go

在go中,如何控制对文本文件的并发写入?

我问这个是因为我将使用相同的文件处理程序将多个goroutine写入文本文件.

我写了这段代码试着看看会发生什么,但我不确定我是否"正确"做到了:

package main

import (
    "os"
    "sync"
    "fmt"
    "time"
    "math/rand"
    "math"
)


func WriteToFile( i int, f *os.File, w *sync.WaitGroup ){
    //sleep for either 200 or 201 milliseconds
    randSleep := int( math.Floor( 200 + ( 2 * rand.Float64() ) ) )
    fmt.Printf( "Thread %d waiting %d\n", i, randSleep )
    time.Sleep( time.Duration(randSleep) * time.Millisecond )

    //write to the file
    fmt.Fprintf( f, "Printing out: %d\n", i )
    //write to stdout
    fmt.Printf( "Printing out: %d\n", i )
    w.Done()
}

func main() {
    rand.Seed( time.Now().UnixNano() )

    d, err := os.Getwd()
    if err != nil {
        fmt.Println( err )
    }
    filename := d + "/log.txt"

    f, err := os.OpenFile( filename, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, 0666 )

    if err != nil {
        fmt.Println( err )
    }
    var w *sync.WaitGroup = new(sync.WaitGroup)
    w.Add( 10 )

    //start 10 writers to the file
    for i:=1; i <= 10; i++ {
        go WriteToFile( i, f, w )
    }

    //wait for writers to finish
    w.Wait()

}
Run Code Online (Sandbox Code Playgroud)

我一半期望输出会在文件中显示类似的内容而不是我得到的相干输出:

Printing Printing out: 2
out: 5
Poriuntitng: 6
Run Code Online (Sandbox Code Playgroud)

从本质上讲,我预计角色会因为缺乏同步而不连贯地出现并交织在一起.我没有编写会哄骗这种行为的代码吗?或者是在调用fmt.Fprintf同步写入期间的某种机制?

Ric*_*777 24

控制并发访问的一种简单方法是通过服务goroutine,从通道接收消息.这个goroutine可以单独访问该文件.因此,访问将是连续的,没有任何种族问题.

通道可以很好地处理交错请求.客户端写入通道而不是直接写入文件.频道上的消息会自动交错.

简单地使用Mutex这种方法的好处是您开始将程序视为微服务的集合.这是CSP方式,可以从较小的组件轻松组合大型系统.

  • 这取决于应用程序,但“正常”情况将涉及在开始时打开文件一次,然后遍历输入通道上的消息,将每条消息写入文件。当通道关闭时,可以关闭文件。 (3认同)

Cal*_*leb 9

有许多方法可以控制并发访问.最简单的方法是使用Mutex:

var mu sync.Mutex

func WriteToFile( i int, f *os.File, w *sync.WaitGroup ){
    mu.Lock()
    defer mu.Unlock()
    // etc...
}
Run Code Online (Sandbox Code Playgroud)

至于为什么你没有看到问题,Go使用操作系统调用来实现文件访问,这些系统调用是线程安全的(强调添加):

根据POSIX.1-2008/SUSv4 Section XSI 2.9.7("与常规文件操作的线程交互"):

当对常规文件或符号链接进行操作时,以下所有函数在POSIX.1-2008中指定的效果中应相互原子化:...

随后列出的API包括write()和writev(2).在跨线程(和进程)应该是原子的效果之中是文件偏移的更新.但是,在版本3.14之前的Linux上,情况并非如此:如果两个共享打开文件描述的进程(请参阅open(2))同时执行write()(或writev(2)),那么我/ O操作在更新文件偏移方面不是原子操作,结果是两个进程输出的数据块可能(错误地)重叠. 在Linux 3.14中修复了此问题.

我仍然会使用锁,因为Go代码不是自动线程安全的.(修改同一个变量的两个goroutine会导致奇怪的行为)