Go
golang

もう少しスマートにやる方法はないものか

More than 1 year has passed since last update.

idが詰まったsliceがある。(本当はidだけではないが話を簡単にするためにidのみとする)
そのsliceの要素1個1個に「なにかしらの処理」を加えつつ、処理結果を「1つのsliceに詰める」
これをx並列で行う。(とりあえずxの数は4とする)

処理中に起きてほしくないことが起きたときは、

1.最初に起きたエラーの内容を取る
2.すべての処理を即座に止める

2を完全に満たすのはこの要件である限り無理では…?と思いはしたので「ある程度はしょうがない」ものとした。してしまった。くるしい。

package main

import (
    "errors"
    "fmt"
    "math/rand"
    "strconv"
    "sync"
    "time"
)

var ids = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}

func main() {
    rand.Seed(time.Now().UnixNano())
    var wg sync.WaitGroup
    var once sync.Once
    // 並列数制御用
    c := make(chan bool, 4)
    // 失敗検知用
    failC := make(chan struct{})
    // 外部に伝えたい気持ち
    ech := make(chan error, 1)
    mt := &sync.Mutex{}
    result := []string{}

    for _, id := range ids {
        wg.Add(1)
        select {
        case <-failC:
            fmt.Println("fail break")
            break
        default:
            // 渡してやらないとidの重複がでる場合がある...
            go func(id int) {
                c <- true
                fmt.Println("start id:" + strconv.Itoa(id))
                defer func() {
                    <-c
                    wg.Done()
                }()
                select {
                case <-failC:
                    fmt.Println("fail goroutine")
                    return
                default:
                    r := rand.Intn(10)
                    if r == 1 {
                        once.Do(func() {
                            fmt.Println("once id:" + strconv.Itoa(id))
                            ech <- errors.New("ERROR!!")
                            close(failC)
                        })
                        fmt.Println("rand hit id:" + strconv.Itoa(id))
                        return
                    }
                    mt.Lock()
                    result = append(result, "yes_"+strconv.Itoa(id))
                    mt.Unlock()
                }

            }(id)
        }
    }
    wg.Wait()
    if len(ech) > 0 {
        err := <-ech
        fmt.Println(err.Error())
    }
    fmt.Println(result)
    fmt.Println("owari")
}

スマートさ云々の前に、一度も fail break が出力されなかった。
件数が少ないからなのか…?
流石にすべてのgoroutineを生成し終わるまでfuncの中身を処理しない、ってことはあるまい。
うーん。。
errgroupパッケージはgo funcにid渡せなかったんですよね。

    eg.Go(func(id int) error {
        //しょり      
    }(id))

ってやったらid渡せなくて断念。
うーーーーーーん