Статьи

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

Как часть сценария Go, над которым я работал, я хотел записать в файл CSV несколько процедур Go, но понял, что встроенный модуль записи CSV не является поточно-ориентированным.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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()
}

основной импорт пакета («encoding / csv» «os» «log» «strconv») func main () {csvFile, err: = os.Create («/ tmp / foo.csv»), если err! = nil {log. Panic (err)} w: = csv.NewWriter (csvFile) w.Write ([] string {“id1 ″,” id2 ″, ”id3”}) count: = 100 done: = make (chan bool, count) для я: = 0; я <считать; i ++ {go func (i int) {w.Write ([] string {strconv.Itoa (i), strconv.Itoa (i), strconv.Itoa (i)}) done <- true} (i)} для i : = 0; я <считать; i ++ {<- done} w.Flush ()}

Этот скрипт должен выводить числа от 0 до 99 три раза в каждой строке. Некоторые строки в файле написаны правильно, но, как мы видим ниже, некоторые нет:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
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. Я написал следующий код для этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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, так что только одна подпрограмма go может одновременно получить доступ к базовому CsvWriter . Затем мы настраиваем начальный скрипт для вызова этого класса вместо прямого вызова CsvWriter:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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, все строки были успешно написаны:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
...
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
...

Это все на данный момент. Если у вас есть предложения для лучшего способа сделать это, дайте мне знать в комментариях или в твиттере — я @markhneedham

Будьте общительны, делитесь!

Ссылка: Go: Многопоточная запись в файл CSV от нашего партнера по JCG Марка Нидхэма в блоге Марка Нидхэма .