2013-12-19 99 views
5

我試圖將日誌消息存儲在緩衝區中,以便只在出現錯誤時才訪問它們。有點像Smarter log handling, the case for opportunistic logging。在這個例子中,我每隔5秒從緩衝區獲取日誌,但是當我使用go run -race code.go運行它時,我得到了一個數據競爭。爲什麼在這個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) 
} 
+1

。該行將logBu​​ffer設置爲正在使用的全局變量。 – rexposadas

回答

6

log包使用內部緩衝區,以積聚用於輸出日誌消息(在log/Loggerbuf字段)。它組成頭部,追加調用者提供的數據,然後將此緩衝區傳遞給您的Write方法以進行輸出。

爲了減少分配,log包爲每個日誌消息回收此緩衝區。在文檔中沒有說明,但隱含的假設是您的Write方法僅在Write調用期間使用提供的[]byte數據。這種假設對於大多數輸出​​是可以的,例如一個文件或標準輸出。

爲了避免數據的比賽,你需要從Write函數返回之前,在輸入數據的明確副本:

如果您刪除log.SetOutput(日誌緩衝區)競賽警告消失
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 
} 
+2

僅供參考,我做了一個類似的緩衝記錄器,它使用了一個'sync.Mutex'保護的'ring.Ring'(循環緩衝區)字符串。 – lnmx

+0

在此方法中使用'sync.Mutex'保護的'ring.Ring'有什麼好處?你有鏈接嗎? – brunoqc

+2

使用'ring.Ring'作爲存儲意味着「讀取」日誌緩衝區並不能清除它,即多個讀者可以獲取最後的'n'個消息。這對我的應用程序很有用。 – lnmx