ゴルーチンを利用したフォルダ・ファイルの作成

Go といえば、並列処理が簡単に書けるゴルーチンということで、自分の備忘も含めて試してみました。

#処理内容

特定のフォルダ(sampleData)配下に、A～Zの26フォルダを作成し
そのフォルダ配下にFileA.txt ～ FileG.txt の7ファイルを作成する。
ファイルの内容は、0～100までの乱数を、1,000行作成する。

上記の処理をゴルーチンを利用した場合と、利用しなかった場合の速度を計測する。


#ゴルーチンを利用しない場合のソース

```go:main.go
package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"
)

func main() {
	DATE_TIME_FORMAT := "15:04:05"
	start := time.Now().Format(DATE_TIME_FORMAT)
	fmt.Println("処理開始", start)

	// フォルダの移動
	os.Chdir("./sampleData")
	//現在のディレトリの確認
	//p, _ := os.Getwd()
	//fmt.Println(p)

	// 作成するフォルダの名称を格納
	var folderNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
		"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
	//var folderNames = []string{"A", "B", "C"}

	for _, folderName := range folderNames {
		// fmt.Println(folderName)
		// フォルダの作成
		if err := os.Mkdir(folderName, 0777); err != nil {
			fmt.Println(err)
		}
		// 作成したフォルダへ移動
		os.Chdir(folderName)
		//現在のディレトリの確認
		p, _ := os.Getwd()
		//fmt.Println(p)

		//作成したフォルダ配下にファイルを作成
		makeFile(p)

		// 元のフォルダへ移動
		os.Chdir("../")
	}

	end := time.Now().Format(DATE_TIME_FORMAT)
	fmt.Println("処理終了", end)
}

func makeFile(filePath string) {
	// 作成するファイルの名称を格納
	var fileNames = []string{"FileA.txt", "FileB.txt", "FileC.txt", "FileD.txt", "FileE.txt", "FileF.txt", "FileG.txt"}
	for _, fileName := range fileNames {

		// ファイルの作成
		// os.O_RDWRを渡すと同時に読み込みも可能　O_APPEND：追記　O_CREATE：なければ作成
		sampleFile, err := os.OpenFile(filePath+"/"+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)
		if err != nil {
			//エラー処理
			log.Fatal(err)
		}
		defer sampleFile.Close() // ファイルのクローズ処理

		for i := 0; i < 1000; i++ {
			// ランダムな値を作成
			time.Sleep(1) // 1ナノ秒待つ　Seedを使っているが、これがないと同じ値が２回くらい続いてしまう
			rand.Seed(time.Now().UnixNano())
			fmt.Fprintln(sampleFile, rand.Intn(100)) //書き込み
		}
	}
}
```

##結果

```cmd:
処理開始 13:39:11
処理終了 13:43:41
```

処理時間は、4分30秒。

#ゴルーチンを利用した場合のソース

```go:main.go
package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"sync"
	"time"
)

func main() {
	DATE_TIME_FORMAT := "15:04:05"
	start := time.Now().Format(DATE_TIME_FORMAT)
	fmt.Println("処理開始", start)

	// フォルダの移動
	os.Chdir("./sampleData")
	//現在のフォルダの確認
	//p, _ := os.Getwd()
	//fmt.Println(p)

	// 作成するフォルダの名称を格納
	var folderNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
		"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
	//var folderNames = []string{"A", "B", "C"}

	var wg sync.WaitGroup

	for _, folderName := range folderNames {
		// fmt.Println(folderName)
		// フォルダの作成
		if err := os.Mkdir(folderName, 0777); err != nil {
			fmt.Println(err)
		}
		// 作成したフォルダへ移動
		os.Chdir(folderName)
		//現在のディレトリの確認
		p, _ := os.Getwd()
		//fmt.Println(p)

		//作成したフォルダ配下にファイルを作成
		wg.Add(1) // WaitGroupに、ゴルーチンを1つずつ追加
		go makeFile(p, &wg)

		// 元のフォルダへ移動
		os.Chdir("../")
	}

	wg.Wait() // すべての並列処理が終わるのをまつ

	end := time.Now().Format(DATE_TIME_FORMAT)
	fmt.Println("処理終了", end)
}

func makeFile(filePath string, wg *sync.WaitGroup) {
	defer wg.Done() // 並列処理完了
	// 作成するファイルの名称を格納
	var fileNames = []string{"FileA.txt", "FileB.txt", "FileC.txt", "FileD.txt", "FileE.txt", "FileF.txt", "FileG.txt"}
	for _, fileName := range fileNames {

		// ファイルの作成
		// os.O_RDWRを渡すと同時に読み込みも可能　O_APPEND：追記　O_CREATE：なければ作成
		sampleFile, err := os.OpenFile(filePath+"/"+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 06666)
		if err != nil {
			//エラー処理
			log.Fatal(err)
		}
		defer sampleFile.Close() // ファイルのクローズ処理

		for i := 0; i < 1000; i++ {
			// ランダムな値を作成
			time.Sleep(1) // 1ナノ秒待つ　Seedを使っているが、これがないと同じ値が２回くらい続いてしまう
			rand.Seed(time.Now().UnixNano())
			fmt.Fprintln(sampleFile, rand.Intn(100)) //書き込み
		}
	}
}
```

##修正箇所

修正箇所は、フォルダ作成のforループ前に`sync.WaitGroup`の宣言を加えています。
これで、複数のgoroutineの完了を待つための値を設定できます。

```go:
	var wg sync.WaitGroup
```

`wg.Add(1)`で、ゴルーチンの処理が1つ追加されたことを設定します。
関数内で処理している `wg.Done()` で、デクリメントされ 0になると、すべての処理が完了したと判断されるようです。

また、`makeFile(p)` を `go makeFile(p, &wg)` に修正しています。
`go` を付与するだけで並列処理になります。
また、先ほど宣言した `wg` を引数として渡し、
関数内で `wg.Done()`をしています。

```go:
wg.Add(1) // WaitGroupに、ゴルーチンを1つずつ追加
go makeFile(p, &wg)

func makeFile(filePath string, wg *sync.WaitGroup) {
	defer wg.Done() // 並列処理完了

```

`makeFile`の引数に、`*sync.WaitGroup`を加え、 `defer wg.Done()` を加えて
ゴルーチンの処理数をデクリメントしています。
`defer` にしたのは途中のエラー処理でエラーが発生した場合、ゴルーチンの完了を待ち続ける可能性があるためです。



##結果

```cmd:
処理開始 13:45:49
処理終了 13:46:00
```

処理時間は、11秒。

#まとめ
ゴルーチンを利用しない場合、フォルダが順番に作成されるのが目で確認できる。
これに対して、ゴルーチンを利用した場合、一瞬ですべてのフォルダが作成され
それぞれのフォルダ内でファイルが作成されているのが確認できた。

夜間バッチなどで、ルールの決まったフォルダ構成に対する処理、
例えば、日付がフォルダ名になっていて、配下の大量のファイルや画像に対して
処理を行う場合など、有効に活用できると思う。

しかし、少しでも書き方を間違えると、トラブルになる可能性大である。

#参考
[sync.WaitGroupの正しい使い方](https://qiita.com/ruiu/items/dba58f7b03a9a2ffad65)