Go 并发写入文件

Go 并发写入文件,go,synchronization,Go,Synchronization,在go中,如何控制对文本文件的并发写入 我这样做是因为我将有多个goroutine使用同一个文件处理程序写入文本文件 我写这段代码是为了尝试看看会发生什么,但我不确定我做得是否“正确”: 从本质上说,由于缺乏同步性,我预计角色会不连贯地出现并交织在一起。我不是写了能诱使这种行为发生的代码吗?或者在调用fmt.Fprintf同步写入过程中是否存在某种机制?有许多方法可以控制并发访问。最简单的方法是使用: 至于为什么您没有看到问题,Go使用操作系统调用来实现文件访问,这些系统调用是(重点添加): 根

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

我这样做是因为我将有多个goroutine使用同一个文件处理程序写入文本文件

我写这段代码是为了尝试看看会发生什么,但我不确定我做得是否“正确”:


从本质上说,由于缺乏同步性,我预计角色会不连贯地出现并交织在一起。我不是写了能诱使这种行为发生的代码吗?或者在调用
fmt.Fprintf
同步写入过程中是否存在某种机制?

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

至于为什么您没有看到问题,Go使用操作系统调用来实现文件访问,这些系统调用是(重点添加):

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

以下所有功能均应为原子功能 在POSIX.1-2008中规定的效果中相互作用 对常规文件或符号链接进行操作:

随后列出的API包括write()和writev(2)。及 在跨线程的原子效果中(和 进程)是文件偏移量的更新。但是,在以前的Linux上 版本3.14时,情况并非如此:如果两个进程共享一个 打开文件说明(请参阅打开(2))执行写入()(或写入(2)) 同时,I/O操作也不是原子的 关于更新文件偏移量,其结果是 两个进程输出的数据可能(错误地)重叠这个 这个问题在Linux 3.14中得到了修复。


尽管如此,我还是会使用锁,因为Go代码不是自动线程安全的。(两个goroutine修改同一个变量将导致奇怪的行为)

控制并发访问的简单方法是通过服务goroutine,从通道接收消息。此goroutine将拥有对该文件的唯一访问权限。因此,访问将是顺序的,没有任何竞争问题

通道在交叉请求方面做得很好。客户端写入通道,而不是直接写入文件。频道上的消息会自动为您交错


与简单地使用互斥体相比,这种方法的好处是,您开始将程序视为一组微服务。这是CSP方式,可以使大型系统从较小的组件轻松组合而成。

我不确定它在其他操作系统上是如何工作的。。但一般来说,在Windows上写入StdOut时至少在换行时获取锁。@SimonWhitehead所以您是说操作系统可能在较低级别处理锁,并且可能无法在go中直接处理锁。不知道CSP代表什么?通信顺序进程()-牛津大学Tony Hoare的过程代数除了服务goroutine外,您是否会在每次收到新消息时打开/关闭文件?或者你会让它一直打开直到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()

}
Printing Printing out: 2
out: 5
Poriuntitng: 6
var mu sync.Mutex

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