#EnablementWorkshop メモ帳
#EnablementWorkshop
レポジトリ
初期プロジェクトの作成
mkdir hoge
cd hoge
go mod init examole.com/hoge
画像変換ツール作成ハンズオン
ソースコード
雑多にコメントを入れてます
実行時に引数にpng画像データの入ったフォルダ指定する
非同期的に使えるやつっぽい
//これは何?ctx := context.Background()
context.Background()を使っていますが、このコードの中では特にcontext.Contextの機能(キャンセルやタイムアウト、値の保存など)は利用されていません。しかし、runやconvertなどの関数がcontext.Contextを引数として受け取る設計となっているため、将来的にキャンセルやタイムアウトなどの機能を追加する際に便利です。
コンテキストに何かを返せばそれをいい感じに利用できるっぽい
deferは便利そう
ログ系
プロファイラー的な奴
めっちゃ見やすい
並列的?にやるようにしたい
実行中!
Windowsだとワイルドカードもファイル名と認識されるっぽい
これで行けた
files, err := filepath.Glob(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx := context.Background()
if err := run(ctx, files); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
- トレースファイル作成
- トレース実行
- コンバートオール内でコンバート関数を回す
- 全部実行まで待つ
- 終了
たぶん実装できた
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert All")
defer task.End()
for _, file := range files {
//convertを呼ぶ
if err := convert(ctx, file); err != nil {
return err
}
}
//成功したらnilを返す
return nil
}
これで実行できた!
go tool trace trace.out
Goのasync/await
ゴール―チンについて
戻り値を受け取りずらいっぽい
Unityのコルーチンと似てる気がする。
名前が似ているから処理方法も似ているのかも
sync.WaitGroupについて
UniTaskのWaitAllと全く同じ処理っぽい
Sync.Mutexについて
めっちゃイメージしやすい
tryLockという関数もあるらしい
処理待ちみたいなやつ
アンロックするで以下の実行はできないやつ
交通整理に例える。
UniTaskはまんまGoっぽい処理を目指していたのかと気が付いた件
チャネルもGoでつかえるっぽい
ここまでのお手本
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
"runtime/trace"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
f, err := os.Create("trace.out")
if err != nil {
return err
}
defer f.Close()
if err := trace.Start(f); err != nil {
return err
}
if err := convertAll(ctx, files); err != nil {
return err
}
trace.Stop()
if err := f.Sync(); err != nil {
return err
}
return nil
}
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert all")
defer task.End()
for _, file := range files {
if err := convert(ctx, file); err != nil {
return err
}
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
defer trace.StartRegion(ctx, "convert "+file).End()
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}
Taskを使ってみた (これはミス)
func convertAll(ctx context.Context, files []string) error {
var wg sync.WaitGroup
ctx, task := trace.NewTask(ctx, "convert All")
defer task.End()
for _, file := range files {
wg.Add(1)
go func(file string) {
defer wg.Done()
//convertを呼ぶ
if err := convert(ctx, file); err != nil {
fmt.Println(err) //エラーを表示す
}
}(file)
}
wg.Wait()
//成功したらnilを返す
return nil
}
正解
func convertAll(ctx context.Context, files []string) error {
var wg sync.WaitGroup
ctx, task := trace.NewTask(ctx, "convert All")
defer task.End()
var (
mu sync.Mutex
rerr error
)
for _, file := range files {
file := file
wg.Add(1)
go func(file string) {
defer wg.Done()
//convertを呼ぶ
if err := convert(ctx, file); err != nil {
mu.Lock()//
if rerr == nil {
rerr = err
}
mu.Unlock()
}
}(file)
}
wg.Wait()
//成功したらnilを返す
return nil
}
Mutexでデータ競合を防ぐ
複数のゴルーチンが同時に共有データにアクセスすることを防ぎ、データ競合(Race Condition)を防止する
mu.Lock()とmu.Unlock()の間のコードはクリティカルセクションと呼ばれ、一度に一つのゴルーチンからしかアクセスできない
一回一回の処理をtaskに貯めてGoルーチンに流して並行的に処理する。
全てのタスクが完了したらwg.Wait
deferは関数の終了時、retarn時に実行するやつ
for分のGoルーチンで値の競合が起こるかも
for _, file := range files {
file := file //ここでイテレーションを行うことで防げる(デベロップ版ではデフォルトでこの処理を入れられるっぽい)
wg.Add(1)
go func(file string) {
defer wg.Done()
//convertを呼ぶ
if err := convert(ctx, file); err != nil {
mu.Lock()
if rerr == nil {
rerr = err//errors.Join(rrer,err)でエラーをくっつけることもできる
}
mu.Unlock()
}
}(file)
}
wg.Wait()
//成功したらnilを返す
return nil
}
Gotipで
デベロップ版?を実行できる
エラー系
エラーを一つのオブジェクトにまとめられるっぽい
並行処理で複数エラーがあった場合一番最初に来たエラーを返すっぽい
eg.Waitで一つでもえあらーが出たら他のゴール―チンをとめる?要確認
コンテキストにエラーが返ってきたらそれ以降の処理を行わせないとかできるっぽい
WaitGroupeもMutexも削除かな?
var eg errgroup.Group
ctx, task := trace.NewTask(ctx, "convert All")
defer task.End()
for _, file := range files {
file := file
eg.Go(func() error {
return convert(ctx, file)
})
}
コンテキストを監視
func convert(ctx context.Context, file string) error {
region := trace.StartRegion(ctx, "convert")
defer region.End()
//ファイルを開く
src, err := os.Open(file)
if err != nil {
return err
}
//閉じるのを予約
defer src.Close()
//ピング画像をデコード
pngimg, err := png.Decode(src)
//エラーがあればその都度返す
if err != nil {
return err
}
//でかい実行後に監視かな?
select {
case <-ctx.Done():
return ctx.Err()
default:
}
関数の一番最初がいいのかな?
ダミーのエラーpngファイルを入れて実行してみる
止まった
Goの実行が速すぎて全部変換し終わっている;;
以下の状態で実行!
結果(コンテキストの監視がうまく成功した!)
パニックをハンドリングしよう
エラーグループ代わりにプールを使う(サードパーティ)
WithFirstErrosがエラーグループと同じ挙動のやつ
ゴール―チン処理内のエラー、パニックをどうハンドリングするのか考えて実装しなければならない。
復習
- ゴール―チンの管理
- Task,Await
- panicsを使いこめるようにする
- コンテキストを管理
- トレースをよく見られるようにする
正解
-- step04/main.go --
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
"runtime"
"runtime/trace"
"github.com/sourcegraph/conc/pool"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
f, err := os.Create("trace.out")
if err != nil {
return err
}
defer f.Close()
if err := trace.Start(f); err != nil {
return err
}
defer trace.Stop()
if err := convertAll(ctx, files); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
return nil
}
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert all")
defer task.End()
pool := pool.New().WithErrors().WithMaxGoroutines(runtime.GOMAXPROCS(0)).WithContext(ctx)
for _, file := range files {
file := file
pool.Go(func(ctx context.Context) error {
if err := convert(ctx, file); err != nil {
return err
}
return nil
})
}
if err := pool.Wait(); err != nil {
return err
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
defer trace.StartRegion(ctx, "convert "+file).End()
select {
case <-ctx.Done():
return ctx.Err()
default:
}
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}
-- step03/main.go --
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
"runtime"
"runtime/trace"
"golang.org/x/sync/errgroup"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
f, err := os.Create("trace.out")
if err != nil {
return err
}
defer f.Close()
if err := trace.Start(f); err != nil {
return err
}
defer trace.Stop()
if err := convertAll(ctx, files); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
return nil
}
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert all")
defer task.End()
eg, ctx := errgroup.WithContext(ctx)
eg.SetLimit(runtime.GOMAXPROCS(0))
for _, file := range files {
file := file
eg.Go(func() error {
if err := convert(ctx, file); err != nil {
return err
}
return nil
})
}
if err := eg.Wait(); err != nil {
return err
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
defer trace.StartRegion(ctx, "convert "+file).End()
select {
case <-ctx.Done():
return ctx.Err()
default:
}
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}
-- step02/main.go --
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
"runtime/trace"
"sync"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
f, err := os.Create("trace.out")
if err != nil {
return err
}
defer f.Close()
if err := trace.Start(f); err != nil {
return err
}
defer trace.Stop()
if err := convertAll(ctx, files); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
return nil
}
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert all")
defer task.End()
var (
mu sync.Mutex
rerr error
)
var wg sync.WaitGroup
for _, file := range files {
file := file
wg.Add(1)
go func() {
defer wg.Done()
if err := convert(ctx, file); err != nil {
mu.Lock()
rerr = errors.Join(rerr, err)
mu.Unlock()
}
}()
}
wg.Wait()
if rerr != nil {
return rerr
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
defer trace.StartRegion(ctx, "convert "+file).End()
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}
-- step05/main.go --
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
"runtime"
"runtime/trace"
"github.com/sourcegraph/conc/panics"
"github.com/sourcegraph/conc/pool"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
f, err := os.Create("trace.out")
if err != nil {
return err
}
defer f.Close()
if err := trace.Start(f); err != nil {
return err
}
defer trace.Stop()
if err := convertAll(ctx, files); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
return nil
}
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert all")
defer task.End()
pool := pool.New().WithErrors().WithMaxGoroutines(runtime.GOMAXPROCS(0)).WithContext(ctx)
for _, file := range files {
file := file
pool.Go(func(ctx context.Context) (rerr error) {
var c panics.Catcher
defer func() {
if r := c.Recovered(); r != nil {
rerr = r.AsError()
}
}()
c.Try(func() { rerr = convert(ctx, file) })
return nil
})
}
if err := pool.Wait(); err != nil {
return err
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
defer trace.StartRegion(ctx, "convert "+file).End()
select {
case <-ctx.Done():
return ctx.Err()
default:
}
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}
-- step00/main.go --
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
for _, file := range files {
if err := convert(ctx, file); err != nil {
return err
}
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}
-- step01/main.go --
package main
import (
"context"
"errors"
"fmt"
jpeg "image/jpeg"
png "image/png"
"os"
"path/filepath"
"runtime/trace"
)
func main() {
ctx := context.Background()
if err := run(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, files []string) error {
f, err := os.Create("trace.out")
if err != nil {
return err
}
defer f.Close()
if err := trace.Start(f); err != nil {
return err
}
defer trace.Stop()
if err := convertAll(ctx, files); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
return nil
}
func convertAll(ctx context.Context, files []string) error {
ctx, task := trace.NewTask(ctx, "convert all")
defer task.End()
for _, file := range files {
if err := convert(ctx, file); err != nil {
return err
}
}
return nil
}
func convert(ctx context.Context, file string) (rerr error) {
defer trace.StartRegion(ctx, "convert "+file).End()
src, err := os.Open(file)
if err != nil {
return err
}
defer src.Close()
pngimg, err := png.Decode(src)
if err != nil {
return err
}
ext := filepath.Ext(file)
jpgfile := file[:len(file)-len(ext)] + ".jpg"
dst, err := os.Create(jpgfile)
if err != nil {
return err
}
defer func() {
dst.Close()
if rerr != nil {
rerr = errors.Join(rerr, os.Remove(jpgfile))
}
}()
if err := jpeg.Encode(dst, pngimg, nil); err != nil {
return err
}
if err := dst.Sync(); err != nil {
return err
}
return nil
}