并发写入文件

时间:2015-05-01 02:59:45

标签: go synchronization

在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()

}

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

Printing Printing out: 2
out: 5
Poriuntitng: 6

基本上,我预计由于缺乏同步,角色会出现不连贯和交织。我没有编写会哄骗这种行为的代码吗?或者是在调用fmt.Fprintf同步写作时的某种机制?

2 个答案:

答案 0 :(得分:20)

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

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

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

答案 1 :(得分:8)

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

var mu sync.Mutex

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

至于为什么你没有看到问题,Go使用操作系统调用来实现文件访问,那些系统调用是thread safe(强调添加):

  

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

     
    

以下所有函数都应该是原子的       彼此在POSIX.1-2008中指定的效果时       对常规文件或符号链接进行操作:...

  
     

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

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