从ajax请求并发写入文件

huangapple go评论92阅读模式
英文:

Concurrent writing to file from ajax request

问题

我想从一些ajax脚本中将请求写入一个文件。问题出现在当一秒钟内有很多这样的请求时,写入文件所需的时间比请求之间的间隔时间更长,而且当同时有两个请求时会出现问题。

我该如何解决这个问题?

我想到了使用互斥锁(mutex),像这样:

var mu sync.Mutex
func writeToFile() {
    mu.Lock()
    defer mu.Unlock()
    // 写入文件
}

但这会使整个过程变成同步的,而且我不太清楚当同时有两个请求时会发生什么。而且它仍然没有锁定文件本身。

嗯,有没有更好的方法来解决这个问题?

英文:

I want to write requests to one file from some ajax script. The problem arises when there will be many of those in a second and writing to file will take more time than the break between requests, and when there will be two requests at the same time.

How could I solve this?

I've came up using mutex, like:

var mu sync.Mutex
func writeToFile() {
    mu.Lock()
    defer mu.Unlock()
    // write to file
}

But it makes the whole thing synchronous and I don't really know what happens when there are two requests at the same time. And it still does not lock the file itself.

Uh, what's the proper way to do this?

答案1

得分: 4

你只需要将写入文件的操作变为“顺序的”,也就是不允许两个并发的goroutine同时写入文件。是的,如果在writeToFile()函数中使用锁定,处理ajax请求可能也会变成(部分)顺序执行。

我建议的做法是在应用程序启动时打开文件一次,并指定一个单独的goroutine负责写入文件,其他goroutine不应该进行写入操作。

使用一个带缓冲的通道来发送需要写入文件的数据。这样可以使得处理ajax请求非阻塞,同时文件也不会被并发或并行写入。

需要注意的是,这种方式下,ajax请求甚至不需要等待数据实际写入文件(响应时间更快)。这可能是个问题,也可能不是问题。例如,如果后续写入失败,你的ajax响应可能已经提交,无法向客户端发送失败信号。

以下是示例代码:

var (
    f      *os.File
    datach = make(chan []byte, 100) // 带缓冲的通道
)

func init() {
    // 打开文件以追加方式(如果不存在则创建)
    var err error
    f, err = os.OpenFile("data.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
    if err != nil {
        panic(err)
    }

    // 启动负责写入文件的goroutine
    go writeToFile()
}

func writeToFile() {
    // 循环处理需要写入的数据:
    for data := range datach {
        if _, err := f.Write(data); err != nil {
            // 处理错误!
        }
    }
    // 当datach被关闭时,执行到这里:关闭文件
    f.Close()
}

func ajaxHandler(w http.ResponseWriter, r *http.Request) {
    // 组装需要写入(追加)到文件的数据
    data := []byte{1, 2, 3}
    // 发送数据:
    datach <- data
}

为了优雅地退出应用程序,你应该关闭datach通道:当它被关闭时,writeToFile()中的循环将终止,文件将被关闭(刷新任何缓存数据,释放操作系统资源)。

如果你想要写入文本到文件中,可以将data通道声明为:

var datach = make(chan string, 100) // 带缓冲的通道

并使用File.WriteString()将其写入文件:

if _, err := f.WriteString(data); err != nil {
    // 处理错误!
}
英文:

You only need to make writing to the file "sequential", meaning don't allow 2 concurrent goroutines to write to the file. Yes, if you use locking in the writeToFile() function, serving your ajax requests may become (partially) sequential too.

What I suggest is open the file once, when your application starts. And designate a single goroutine which will be responsible writing to the file, no other goroutines should do it.

And use a buffered channel to send data that should be written to the file. This will make serving ajax requests non-blocking, and still the file will not be written concurrently / parallel.

Note that this way ajax requests won't even have to wait while the data is actually written to the file (faster response time). This may or may not be a problem. For example if later writing fails, your ajax response might already be committed => no chance to signal failure to the client.

Example how to do it:

var (
	f      *os.File
	datach = make(chan []byte, 100) // Buffered channel
)

func init() {
	// Open file for appending (create if doesn&#39;t exist)
	var err error
	f, err = os.OpenFile(&quot;data.txt&quot;, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}

	// Start goroutine which writes to the file
	go writeToFile()
}

func writeToFile() {
	// Loop through any data that needs to be written:
	for data := range datach {
		if _, err := f.Write(data); err != nil {
			// handle error!
		}
	}
	// We get here if datach is closed: shutdown
	f.Close()
}

func ajaxHandler(w http.ResponseWriter, r *http.Request) {
	// Assmeble data that needs to be written (appended) to the file
	data := []byte{1, 2, 3}
	// And send it:
	datach &lt;- data
}

To gracefully exit from the app, you should close the datach channel: when it's closed, the loop in the writeToFile() will terminate, and the file will be closed (flushing any cached data, releasing OS resources).

If you want to write text to the file, you may declare the data channel like this:

var datach = make(chan string, 100) // Buffered channel

And you may use File.WriteString() to write it to the file:

if _, err := f.WriteString(data); err != nil {
    // handle error!
}

huangapple
  • 本文由 发表于 2016年5月24日 18:36:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/37411114.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定