Generator Pattern

Generator PatternはGo言語における並列処理パターンの一つで、goroutine-safeな値列の生成などに使用することができます。 コードを見た方が早いと思いますので、コードを掲載しましょう。 次の例は複数のgoroutineから共通の連番を採番したいときに利用することができます。 1 2 3 4 5 6 7 8 9 10 11 12 13 func GenInt(ctx context.Context, max int) <-chan int { ch := make(chan int) go func() { defer close(ch) for i := 0; i < max; i++ { select { case <-ctx.Done(): return case ch <- i: } } return ch } 返された<-chan intからintの値を取得するようにすることで、重複のない連番を取得することができます。 Go言語において、chanは複数箇所から値の取り出しを行うことができますが、chanに入力された一つの値はどこか一箇所からしか取り出すことができません。そのため、lock等を使用しなくとも、必ず重複無く連番を取得することができます。 lockを使用した場合、若干動作が遅いため、可能であればlockを使用しないで、chanを使用して実装できるとより高速な、Goらしいコードとすることができます。

2018-11-21 · nasa9084

Future Pattern

Future Patternは非同期処理パターンの一つで、ある処理を別のスレッドなどで実行し、結果を後で(=未来で)受け取るような処理に用いられるデザインパターンです。 特徴としては、外側に見えている関数などの処理を実行するオブジェクトは、処理を別スレッドに委譲し、後で結果を得ることの出来るFutureと呼ばれるオブジェクトを即座にメインロジックへと返却することです。 言葉で書いても、何だかよくわからないので、コードを見てみましょう。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /* package, import part */ func main() { in := make(chan int) out := Double(in) // この時点では結果は得られない go func() { for i := 0; i < 10; i++ { in <- i } close(in) }() for d := range out { fmt.Println(d) // ここで結果を得る } } func Double(in <-chan int) <-chan int { out := make(chan int) go func() { for i := range in { out <- 2 * i } close(out) } return out // Futureオブジェクト } main関数から呼び出されたDouble関数は、与えられた数を二倍する関数ですが、二倍する処理は呼び出された時点では実行せず、即座にchannelを返します。この、変数名outのchannelがFutureオブジェクトです。 そのため、数を二倍した結果は、Double関数を呼び出した時点では得られず、後でoutchannelから得ることとなります。 ...

2018-07-04 · nasa9084

Golang: 手軽にシグナルをListenしてcallback関数を呼ぶ

Go言語でシグナルを取り扱いたい場合、osパッケージおよびos/signalパッケージ、syscallパッケージを使用します。 具体的には、以下のようにします。 1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { sigCh := make(chan os.Signal, 1) doneCh := make(chan struct{}) signal.Notify(sigCh, syscall.SIGINT) go func() { sig := <-sigCh fmt.Println(sig) // (1) close(doneCh) }() <-done } 実際には、(1)の様に受け取ったシグナルを出力するだけではなく、何らかの処理を行うことになるでしょうし、goroutineのリークを避けるためにシグナルの待受をキャンセルする必要が有りますから、contextを使用してfor-selectループを書くことにもなるでしょう。 例として、HTTPサーバをシャットダウンするような処理を考えます。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func main() { sigCh := make(chan os.Signal, 1) doneCh := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) signal.Notify(sigCh, syscall.SIGINT) s := &http.Server{ Addr: ":8080", Handler: http.DefaultServeMux, } go func() { for { select { case sig := <-sigCh: sig := <-sigCh s.Shutdown(context.Background()) close(doneCh) case <-ctx.Done(): return } } }() if err := s.ListenAndServe(); err != http.ErrServerClosed { log.Println(err) cancel() return } <-doneCh } シグナルを受け取って、関数の呼び出し(ここではs.Shutdown())をしたいだけなのに、チャンネルを作って、goroutineを立ち上げて、となんとも大仰です。 goroutineで呼び出す関数の中でfor-selectループを使っているため、行数も長くなってしまっています。 ...

2018-03-06 · nasa9084

sync.WaitGroup

Goroutineを使用して複数の処理を並列で実行、すべてが終わったら次の処理に進みたいという場合があると思います。 Goroutineでデータのリストを作るという処理を考えます。 データの順番は関係なく、すべてのGoroutineでのデータがそろったら次の処理をしたいという設定です。 この場合、単純に考えると以下のようなコードになりますが、以下のコードではデータがそろう前に次の処理が行われます。 1 2 3 4 5 6 7 datalist := []string{} for i := 0; i < 10; i++ { go func() { // something w/datalist } } fmt.Println("next step") このような場合に、sync.WaitGroupを使用します。 sync.WaitGroupは基本的にはただのカウンタですが、カウンタがゼロになるまで処理を待つことができます。 言葉で説明してもわかりにくいと思いますので、ソースコードを見てみましょう。 1 2 3 4 5 6 7 8 9 10 11 12 datalist := []string{} wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) // Goroutineの数だけカウンタを増やす go func() { // something w/datalist wg.Done() // カウンタを減らす } } wg.Wait() // カウンタが0になるまでブロックする fmt.Println("next step") 上記の様にすることで、for文の部分ではGoroutineで並列に実行しつつ、次の処理は並列実行部分が終わってからという動作をさせることができます。 ポイントはカウンタを増やす部分です。 Goroutine内ではなく、外側でAdd(1)します。 Goroutine内でAdd(1)してしまうと、wg.Wait()に到達した時点でGoroutineがまだどれも実行されておらず、次の処理に進んでしまう可能性があるので、必ずGoroutineの外側で実行することが肝要です。

2017-09-01 · nasa9084