👻

最後に一回だけ動いてほしい:Go

2022/02/17に公開

どこかにあるかと思ったらなかった。

まずはやりたいことがわかる処理を書きます

package main

import (
	"fmt"
	"time"
)

func main() {
	for idx := 0; idx < 10; idx++ {
		go heavy(idx)
		time.Sleep(500)
	}
}

func heavy(cnt int) {
	for idx := 0; idx < 3; idx++ {
		fmt.Printf("heavy proc-start:%d:%d\n", cnt, idx)
		time.Sleep(1000)
		fmt.Printf("heavy proc-end:%d:%d\n", cnt, idx)
	}
}

はい、この重い処理をしている最中は処理をキューイングして最後に一回動いてくれればいいです。
↑のコードの問題点は以下です

  • heavyの終了待機できてない
  • 複数gorutineが動いちゃう

なんか指示が来たとして最後に一回動いてくれりゃいいんですよ。
とりあえず直していきましょう

終了待機

とりあえず待つならWaitGroup当たりを使ってみようと思います。

package main

import (
	"log"
	"sync"
	"time"
)

func main() {
	log.SetFlags(log.Lmicroseconds)
	wg := new(sync.WaitGroup)
	for idx := 0; idx < 10; idx++ {
		wg.Add(1)
		go heavy(idx, wg)
		time.Sleep(1)
	}
	wg.Wait()
	log.Println("finish!!!")
}

func heavy(cnt int, wg *sync.WaitGroup) {
	defer wg.Done()
	for idx := 0; idx < 3; idx++ {
		log.Printf("heavy proc-start:%d:%d\n", cnt, idx)
		time.Sleep(1000)
		log.Printf("heavy proc-end:%d:%d\n", cnt, idx)
	}
}

これで終了待機できるようになりました。
ログを出して処理順等々を見てますが出力はこんな感じ

23:07:35.789417 heavy proc-start:0:0
23:07:35.801717 heavy proc-start:1:0
23:07:35.817298 heavy proc-start:2:0
23:07:35.832200 heavy proc-start:3:0
23:07:35.840636 heavy proc-start:4:0
23:07:35.847631 heavy proc-end:4:0
23:07:35.847631 heavy proc-start:4:1
23:07:35.847631 heavy proc-end:1:0
23:07:35.847631 heavy proc-start:1:1
23:07:35.847631 heavy proc-end:3:0
23:07:35.847631 heavy proc-start:3:1
23:07:35.847631 heavy proc-end:2:0
23:07:35.848636 heavy proc-start:2:1
23:07:35.848636 heavy proc-start:6:0

proc-start:0:0が呼ばれてからproc-end:0:0が呼ばれるまでは処理が動くのをブロックしたいんですよね。

処理のブロッキング

package main

import (
	"context"
	"log"
	"sync"
	"time"
)

var procFlg bool

func main() {
	log.SetFlags(log.Lmicroseconds)
	wg := new(sync.WaitGroup)
	var mux sync.Mutex
	var before context.CancelFunc
	for idx := 0; idx < 10; idx++ {
		if before != nil {
			log.Println("owarini:", idx-1)
			before()
		}
		ctx, cancel := context.WithCancel(context.Background())

		wg.Add(1)
		go heavy(idx, wg, &mux, ctx)
		time.Sleep(500 * time.Millisecond)
		before = cancel
	}
	wg.Wait()
	log.Println("finish!!!")
}

func heavy(cnt int, wg *sync.WaitGroup, mux *sync.Mutex, ctx context.Context) {
	defer wg.Done()
	mux.Lock()
	defer mux.Unlock()
	select {
	case <-ctx.Done():
		log.Printf("Done:%d", cnt)
		return
	default:
		break
	}
	log.Printf("heavy proc-start:%d\n", cnt)
	time.Sleep(1 * time.Second)
	log.Printf("heavy proc-end:%d\n", cnt)
}

まずはmutexで排他処理をします。
その後前回処理のキャンセルオブジェクトを取得しておいて、次処理が入ってきたら前回の堂田をキャンセルという形にしています。

00:19:51.150539 heavy proc-start:0
00:19:51.654922 owarini: 0
00:19:52.158964 owarini: 1
00:19:52.221026 heavy proc-end:0
00:19:52.221026 Done:1
00:19:52.221576 heavy proc-start:2
00:19:52.674684 owarini: 2
00:19:53.188260 owarini: 3
00:19:53.235450 heavy proc-end:2
00:19:53.235967 Done:3
00:19:53.236030 heavy proc-start:4
00:19:53.701679 owarini: 4
00:19:54.203184 owarini: 5
00:19:54.250171 heavy proc-end:4
00:19:54.250275 Done:5
00:19:54.250847 heavy proc-start:6
00:19:54.706078 owarini: 6
00:19:55.219204 owarini: 7
00:19:55.265566 heavy proc-end:6
00:19:55.265566 Done:7
00:19:55.266225 heavy proc-start:8
00:19:55.722895 owarini: 8
00:19:56.269934 heavy proc-end:8
00:19:56.270007 heavy proc-start:9
00:19:57.283157 heavy proc-end:9
00:19:57.283157 finish!!!

はい、ログからキューイングされている最後の処理が動いてもしすでにされている場合はキャンセルできていそうです。
誰かもっといいのがあったら教えてください。

Discussion