为了账号安全,请及时绑定邮箱和手机立即绑定

为什么这个 Go 程序会出现数据竞赛?

为什么这个 Go 程序会出现数据竞赛?

Go
DIEA 2021-07-08 17:07:10
我试图将日志消息存储在缓冲区中,以便仅在出现错误时访问它们。有点像智能日志处理中的机会日志记录。在这个例子中,我每 5 秒从缓冲区中获取日志,但是当我使用go run -race code.go.我正在使用渠道进行交流,但显然我做错了。package mainimport (    "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)}
查看完整描述

1 回答

?
翻阅古今

TA贡献1780条经验 获得超5个赞

该log包使用内部缓冲区来构建用于输出的日志消息(log/Logger 中的buf字段)。它组成标题,附加调用者提供的数据,然后将此缓冲区传递给您的 方法以进行输出。Write


为了减少分配,log包为每个日志消息回收这个缓冲区。文档中没有说明,但隐含的假设是您的Write方法仅[]byte在Write调用期间使用提供的数据。这个假设适用于大多数输出,例如文件或 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

}


查看完整回答
反对 回复 2021-07-12
  • 1 回答
  • 0 关注
  • 201 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信