Go: многопоточная запись в файл CSV

Go: многопоточная запись в файл CSV
Иногда при разработке приложений на Go бывает необходимость записи в файл CSV из нескольких горутин, при этом встроенный CSV Writer непотокобезопасен.

Моя первая попытка записи в файл CSV выглядела так:

package main

import (
"encoding/csv"
"os"
"log"
"strconv"
)

func main() {

csvFile, err := os.Create("/tmp/foo.csv")
if err != nil {
log.Panic(err)
}

w := csv.NewWriter(csvFile)
w.Write([]string{"id1","id2","id3"})

count := 100
done := make(chan bool, count)

for i := 0; i < count; i++ { go func(i int) { w.Write([]string {strconv.Itoa(i), strconv.Itoa(i), strconv.Itoa(i)}) done <- true }(i) } for i:=0; i < count; i++ { <- done } w.Flush() }
Этот сценарий должен выводить числа от 0 до 99 по три на строку. Некоторые строки записались правильно, но как мы видим, некоторые неправильно:

40,40,40
37,37,37
38,38,38
18,18,39
^@,39,39
...
67,67,70,^@70,70
65,65,65
73,73,73
66,66,66
72,72,72
75,74,75,74,75
74
7779^@,79,77
...

Есть способ, которым мы можем сделать наш код безопасным - использовать мьютекс при вызове методов CSV Writer-а. И я написал такой код:

type CsvWriter struct {
mutex *sync.Mutex
csvWriter *csv.Writer
}

func NewCsvWriter(fileName string) (*CsvWriter, error) {
csvFile, err := os.Create(fileName)
if err != nil {
return nil, err
}
w := csv.NewWriter(csvFile)
return &CsvWriter{csvWriter:w, mutex: &sync.Mutex{}}, nil
}

func (w *CsvWriter) Write(row []string) {
w.mutex.Lock()
w.csvWriter.Write(row)
w.mutex.Unlock()
}

func (w *CsvWriter) Flush() {
w.mutex.Lock()
w.csvWriter.Flush()
w.mutex.Unlock()
}

Мы создаём мьютекс когда NewCsvWriter создаёт экземпляры CsvWriter, затем используем его в функциях Write и Flush, таким образом только одна горутина одновременно имеет доступ к базовому CsvWriter-у. Немного изменим наш код так, чтобы не вызывать CsvWriter напрямую:

func main() {
w, err := NewCsvWriter("/tmp/foo-safe.csv")
if err != nil {
log.Panic(err)
}

w.Write([]string{"id1","id2","id3"})

count := 100
done := make(chan bool, count)

for i := 0; i < count; i++ { go func(i int) { w.Write([]string {strconv.Itoa(i), strconv.Itoa(i), strconv.Itoa(i)}) done <- true }(i) } for i:=0; i < count; i++ { <- done } w.Flush() }
Сейчас если мы посмотрим в файл CSV, то увидим, что все строки корректны:

...
25,25,25
13,13,13
29,29,29
32,32,32
26,26,26
30,30,30
27,27,27
31,31,31
28,28,28
34,34,34
35,35,35
33,33,33
37,37,37
36,36,36
...

Источник: "Go: Multi-threaded writing to a CSV file"

Leave a Comment