我正在尝试将日志消息存储在缓冲区中,以便仅在出现错误时访问它们。有点像 Smarter log handling, the case for opportunistic logging .在这个例子中,我每 5 秒从缓冲区中获取日志,但是当我使用 go run -race code.go
运行它时,我遇到了数据竞争。
我正在使用 channel 进行交流,但显然我做错了什么。
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"time"
)
type LogRequest struct {
Buffer chan []byte
}
type LogBuffer struct {
LogInputChan chan []byte
LogRequests chan LogRequest
}
func (f LogBuffer) Write(b []byte) (n int, err error) {
f.LogInputChan <- b
return len(b), nil
}
func main() {
var logBuffer LogBuffer
logBuffer.LogInputChan = make(chan []byte, 100)
logBuffer.LogRequests = make(chan LogRequest, 100)
log.SetOutput(logBuffer)
// store the log messages in a buffer until we ask for it
go func() {
buf := new(bytes.Buffer)
for {
select {
// receive log messages
case logMessage := <-logBuffer.LogInputChan:
buf.Write(logMessage) // <- data race
case logRequest := <-logBuffer.LogRequests:
c, errReadAll := ioutil.ReadAll(buf)
if errReadAll != nil {
panic(errReadAll)
}
logRequest.Buffer <- c
}
}
}()
// log a test message every 1 second
go func() {
for i := 0; i < 30; i++ {
log.Printf("test: %d", i) // <- data race
time.Sleep(1 * time.Second)
}
}()
// print the log every 5 seconds
go func() {
for {
time.Sleep(5 * time.Second)
var logRequest LogRequest
logRequest.Buffer = make(chan []byte, 1)
logBuffer.LogRequests <- logRequest
buffer := <-logRequest.Buffer
fmt.Printf("**** LOG *****\n%s**** END *****\n\n", buffer)
}
}()
time.Sleep(45 * time.Second)
}
最佳答案
log
包使用内部缓冲区来构建用于输出的日志消息(log/Logger 中的 buf
字段)。它组成 header ,附加调用者提供的数据,然后将此缓冲区传递给您的 Write
方法进行输出。
为了减少分配,log
包为每条日志消息回收此缓冲区。文档中没有说明,但隐含的假设是您的 Write
方法在 Write
期间仅使用提供的 []byte
数据> 打电话。这个假设对于大多数输出都是可行的,例如文件或 STDOUT。
为避免数据竞争,您需要在从 Write
函数返回之前显式复制传入数据:
func (f LogBuffer) Write(b []byte) (n int, err error) {
z := make([]byte, len(b))
copy(z, b)
f.LogInputChan <- z
return len(b), nil
}
关于go - 为什么这个 Go 程序中会出现数据竞争?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20687597/