Open25

#EnablementWorkshop メモ帳

eisukeeisuke

//これは何?ctx := context.Background()

context.Background()を使っていますが、このコードの中では特にcontext.Contextの機能(キャンセルやタイムアウト、値の保存など)は利用されていません。しかし、runやconvertなどの関数がcontext.Contextを引数として受け取る設計となっているため、将来的にキャンセルやタイムアウトなどの機能を追加する際に便利です。

コンテキストに何かを返せばそれをいい感じに利用できるっぽい

eisukeeisuke

実行中!
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)
	}
eisukeeisuke
  1. トレースファイル作成
  2. トレース実行
  3. コンバートオール内でコンバート関数を回す
  4. 全部実行まで待つ
  5. 終了

たぶん実装できた

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

eisukeeisuke

ゴール―チンについて

戻り値を受け取りずらいっぽい
Unityのコルーチンと似てる気がする。
名前が似ているから処理方法も似ているのかも

eisukeeisuke

sync.WaitGroupについて


UniTaskのWaitAllと全く同じ処理っぽい

eisukeeisuke

Sync.Mutexについて


めっちゃイメージしやすい

tryLockという関数もあるらしい

処理待ちみたいなやつ
アンロックするで以下の実行はできないやつ
交通整理に例える。

UniTaskはまんまGoっぽい処理を目指していたのかと気が付いた件

チャネルもGoでつかえるっぽい

eisukeeisuke

ここまでのお手本

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
}

eisukeeisuke

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

eisukeeisuke

deferは関数の終了時、retarn時に実行するやつ

eisukeeisuke

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で

デベロップ版?を実行できる

eisukeeisuke

エラー系

エラーを一つのオブジェクトにまとめられるっぽい
並行処理で複数エラーがあった場合一番最初に来たエラーを返すっぽい
eg.Waitで一つでもえあらーが出たら他のゴール―チンをとめる?要確認

コンテキストにエラーが返ってきたらそれ以降の処理を行わせないとかできるっぽい

eisukeeisuke

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)
		})
	}
eisukeeisuke

コンテキストを監視


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:
	}

関数の一番最初がいいのかな?

eisukeeisuke

ダミーのエラーpngファイルを入れて実行してみる


止まった

Goの実行が速すぎて全部変換し終わっている;;

以下の状態で実行!

結果(コンテキストの監視がうまく成功した!)

eisukeeisuke

パニックをハンドリングしよう


エラーグループ代わりにプールを使う(サードパーティ)


WithFirstErrosがエラーグループと同じ挙動のやつ

eisukeeisuke


ゴール―チン処理内のエラー、パニックをどうハンドリングするのか考えて実装しなければならない。

eisukeeisuke

復習

  • ゴール―チンの管理
  • Task,Await
  • panicsを使いこめるようにする
  • コンテキストを管理
  • トレースをよく見られるようにする
eisukeeisuke

正解

-- 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
}