为什么Go计划中存在数据竞争?

bru*_*oqc 6 go race-condition

我正在尝试将日志消息存储在缓冲区中,以便仅在出现错误时才能访问它们.有点像智能日志处理,机会记录的情况.在这个例子中,我每隔5秒从缓冲区中获取一次日志,但是当我运行它时,我得到一个数据竞争go run -race code.go.

我正在使用渠道进行沟通,但显然我做错了.

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)
}
Run Code Online (Sandbox Code Playgroud)

lnm*_*nmx 7

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

为了减少分配,log程序包会为每个日志消息回收此缓冲区.它没有在文档中说明,但隐含的假设是您的Write方法仅[]byteWrite调用期间使用提供的数据.对于大多数输出​​,例如文件或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
}
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,我做了一个类似的缓冲记录器,它使用了一个`sync.Mutex`-保护的`ring.Ring`(循环缓冲区)字符串. (2认同)
  • 使用`ring.Ring`进行存储意味着"读取"日志缓冲区不会清除它,即多个读取器可以获取最后的"n"消息.这对我的应用程序很有用. (2认同)