英文:
Golang reading from a file - is it safe from locking?
问题
我有一个函数,它会在每个HTTP GET请求上被调用。该函数读取一个文件,对文件内容进行一些处理,并返回这些内容的字节切片。然后,将该字节切片作为HTTP响应的响应体写入。
在这个函数的任何步骤中,我是否需要使用互斥锁来防止在多个HTTP请求尝试读取同一个文件时出现锁定?如果需要,是否只使用一个简单的RWMutex来锁定文件的读取就足够了,因为我实际上并没有对文件进行写入,而是创建了其内容的副本?
以下是该函数的代码:
// prepareIndex函数将获取index.html并为CSP头部合规性的脚本标签添加一个nonce。
func prepareIndex(nonce string) []byte {
// 加载index.html文件。
file, err := os.Open("./client/dist/index.html")
if err != nil {
log.Fatal(err)
}
// 转换为goquery文档。
doc, err := goquery.NewDocumentFromReader(file)
if err != nil {
fmt.Println(err)
}
// 查找所有的脚本标签并设置nonce。
doc.Find("body > script").SetAttr("nonce", nonce)
// 获取HTML字符串。
html, err := doc.Html()
if err != nil {
fmt.Println(err)
}
return []byte(html)
}
我也考虑过在main函数启动时只加载文件一次,但是我遇到了一个问题,只有第一个请求能够看到数据,而后续的请求则看不到任何内容。可能是我读取文件的方式有误。但实际上,我更喜欢当前的方法,因为如果对index.html
进行任何更改,我希望这些更改能够立即传送给用户,而无需重新启动可执行文件。
英文:
I have a function that will be called on every single HTTP GET request. The function reads a file, does some stuff to the contents of that file, and returns a slice of bytes of those contents. That slice of bytes of then written as the response body to the HTTP response writer.
Do I need to use a mutex for any of the steps in this function to prevent locking in the event of multiple HTTP requests trying to read the same file? And if so, would a simple RWMutex locking the reading of the file suffice, since I am not actually writing to it but am creating a copy of its contents?
Here is the function:
// prepareIndex will grab index.html and add a nonce to the script tags for the CSP header compliance.
func prepareIndex(nonce string) []byte {
// Load index.html.
file, err := os.Open("./client/dist/index.html")
if err != nil {
log.Fatal(err)
}
// Convert to goquery document.
doc, err := goquery.NewDocumentFromReader(file)
if err != nil {
fmt.Println(err)
}
// Find all script tags and set nonce.
doc.Find("body > script").SetAttr("nonce", nonce)
// Grab the HTML string.
html, err := doc.Html()
if err != nil {
fmt.Println(err)
}
return []byte(html)
}
I also thought about just loading the file once when main starts, but I was having a problem where only the first request could see the data and the subsequent requests saw nothing. Probably an error in the way I was reading the file. But I actually prefer my current approach because if there are any changes to index.html
, I want them to be persisted to the user immediately without having to restart the executable.
答案1
得分: 2
使用RWMutex
无法保护文件免受其他程序的修改。在这里,最好的选择是在启动时将文件加载到[]byte
中,并在使用goquery.NewDocumentFromReader
时实例化"bytes".Buffer
。为了将更改传播给用户,您可以使用fsnotify1检测文件的更改,并在必要时更新缓存的文件([]byte
)(此操作需要使用RWMutex
)。
例如:
type CachedFile struct {
sync.RWMutex
FileName string
Content []byte
watcher *fsnotify.Watcher
}
func (c *CachedFile) Buffer() *bytes.Buffer {
c.RLock()
defer c.RUnlock()
return bytes.NewBuffer(c.Content)
}
func (c *CachedFile) Load() error {
c.Lock()
content, err := ioutil.ReadAll(c.FileName)
if err != nil {
c.Unlock()
return err
}
c.Content = content
c.Unlock()
}
func (c *CachedFile) Watch() error {
var err error
c.watcher, err = fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
for ev := range c.watcher.Events {
if ev.Op != fsnotify.Write {
continue
}
err := c.Load()
if err != nil {
log.Printf("loading %q: %s", c.FileName, err)
}
}
}()
err = c.watcher.Add(c.FileName)
if err != nil {
c.watcher.Close()
return err
}
return nil
}
func (c *CachedFile) Close() error {
return c.watcher.Close()
}
1 https://godoc.org/github.com/fsnotify/fsnotify
英文:
Using RWMutex
won't protect you from the file being modified by another program. The best option here would be to load your file in a []byte
at startup, and instantiate "bytes".Buffer
whenever you use goquery.NewDocumentFromReader
. In order for the changes to be propagated to the user, you can use fsnotify1 to detect changes to your file, and update your cached file ([]byte
) when necessary (you will need RWMutex
for that operation).
For example:
type CachedFile struct {
sync.RWMutex
FileName string
Content []byte
watcher *fsnotify.Watcher
}
func (c *CachedFile) Buffer() *bytes.Buffer {
c.RLock()
defer c.RUnlock()
return bytes.NewBuffer(c.Content)
}
func (c *CachedFile) Load() error {
c.Lock()
content, err := ioutil.ReadAll(c.FileName)
if err != nil {
c.Unlock()
return err
}
c.Content = content
c.Unlock()
}
func (c *CachedFile) Watch() error {
var err error
c.watcher, err = fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
for ev := range c.watcher.Events {
if ev.Op != fsnotify.Write {
continue
}
err := c.Load()
if err != nil {
log.Printf("loading %q: %s", c.FileName, err)
}
}
}()
err = c.watcher.Add(c.FileName)
if err != nil {
c.watcher.Close()
return err
}
return nil
}
func (c *CachedFile) Close() error {
return c.watcher.Close()
}
答案2
得分: 1
如果你正在修改文件,你需要一个互斥锁。RWMutex
应该可以很好地工作。看起来你只是在读取文件,在这种情况下,你不应该看到任何锁定行为或损坏。
你第二次从同一个文件句柄读取时没有获取到任何数据的原因是,当你第二次开始读取时,你已经在文件的末尾。如果你想再次读取内容,你需要将偏移量0
重新定位到文件开头。
英文:
If you're modifying the file, you need a mutex. RWMutex
should work fine. It looks like you're just reading it, and in that case you should not see any locking behavior or corruption.
The reason you didn't get any data the second time you read from the same file handle is that you're already at the end of the file when you start reading from it the second time. You need to seek back to offset 0
if you want to read the contents again.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论