2013-12-19 15 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

あなたのremove log.SetOutput(logBu​​ffer)レースの警告が表示されなくなります。その行は、goルーチンによって使用されているグローバル変数としてlogBu​​fferを設定します。 – rexposadas

答えて

6

logパッケージがビルドアップするためにログメッセージを出力するための内部バッファ(log/Loggerbufフィールド)を使用しています。ヘッダーを作成し、呼び出し元によって提供されたデータを追加し、このバッファを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 
} 
+2

参考までに、私は文字列の 'sync.Mutex'で保護された' ring.Ring'(循環バッファ)を使った同様のバッファリングされたロガーを行っています。 – lnmx

+0

このメソッドで 'sync.Mutex'で保護された' ring.Ring'を使う利点は何ですか?リンクがありますか? – brunoqc

+2

保管のために「ring.Ring」を使用することは、ログバッファを「読み込む」ことでそれをクリアしない、すなわち複数の読者が最後の「n」メッセージをフェッチすることができることを意味する。それは私のアプリケーションに便利でした。 – lnmx

関連する問題