go - 为什么这个 Go 程序中会出现数据竞争?

标签 go race-condition

我正在尝试将日志消息存储在缓冲区中,以便仅在出现错误时访问它们。有点像 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/

相关文章:

function - 如何将 time.Duration 类型传递给 go 函数?

go - 在 Delve 中通过源文件设置断点

security - 使用 crypto/* 后安全地将缓冲区清零

mysql - 为什么当存在唯一索引时会出现死锁错误

dictionary - 在同步 map 中检测到数据争用情况-Golang

c - 队列数据结构中 isempty 的线程安全实现

go - 发生http超时时,如何获取json.NewDecoder(body).Decode()的错误原因?

loops - 如何打破 Go 中的嵌套循环?

c++ - 在 C++11 中,什么时候将指针传递给另一个线程会出现竞争条件?

c# - 仅使用 async/await 时是否存在竞争条件?