web-dev-qa-db-ja.com

常にx個のゴルーチンが常に実行されている

Goがx個のゴルーチンを完了するまで待機させる方法に関するチュートリアルと例がたくさんありますが、私がしようとしていることは、常にx個が実行されていることを確認しているため、新しいgoroutineが終了するとすぐに起動します。

具体的には、MySQLから出てくるものを処理する数十万の「やるべきこと」があります。したがって、次のように機能します。

_db, err := sql.Open("mysql", connection_string)
checkErr(err)
defer db.Close()

rows,err := db.Query(`SELECT id FROM table`)
checkErr(err)
defer rows.Close()

var id uint
for rows.Next() {
    err := rows.Scan(&id)
    checkErr(err)
    go processTheThing(id)
    }
checkErr(err)
rows.Close()
_

現在、これはprocessTheThing()の数十万スレッドを起動します。私が必要とするのは、最大xの数(20と呼びます)のゴルーチンが起動されることです。したがって、最初の20行で20を起動することから始まり、現在のゴルーチンの1つが終了した瞬間に、次のIDの新しいゴルーチンを起動します。したがって、常に20が実行されています。

これは非常にシンプルで標準的であると確信していますが、チュートリアルや例、またはこれがどのように行われるかについて、適切な説明を見つけることができないようです。

30
Alasdair

これで私を助けてくれたみんなに感謝します。しかし、あなたがすべてこのテクニックを理解するのを助けてくれましたが、誰もが実際に機能し、シンプルで理解できるものを本当に提供したとは思いません。

私が最後にやったことは、私の特定の質問への回答としてはるかに理解可能で実用的だと思うので、他の誰かが同じ質問をする場合に備えて、ここに投稿します。

どういうわけか、これはOneOfOneが投稿したものと非常によく似たものになりました。しかし、関数を関数に渡すため、最初に理解したOneOfOneのコードは、何が何のためにあるのかを理解するのが非常に混乱しました。この方法はもっと理にかなっていると思います:

package main

import (
"fmt"
"sync"
)

const xthreads = 5 // Total number of threads to use, excluding the main() thread

func doSomething(a int) {
    fmt.Println("My job is",a)
    return
}

func main() {
    var ch = make(chan int, 50) // This number 50 can be anything as long as it's larger than xthreads
    var wg sync.WaitGroup

    // This starts xthreads number of goroutines that wait for something to do
    wg.Add(xthreads)
    for i:=0; i<xthreads; i++ {
        go func() {
            for {
                a, ok := <-ch
                if !ok { // if there is nothing to do and the channel has been closed then end the goroutine
                    wg.Done()
                    return
                }
                doSomething(a) // do the thing
            }
        }()
    }

    // Now the jobs can be added to the channel, which is used as a queue
    for i:=0; i<50; i++ {
        ch <- i // add i to the queue
    }

    close(ch) // This tells the goroutines there's nothing else to do
    wg.Wait() // Wait for the threads to finish
}
15
Alasdair

Go Concurrency Patterns の記事、特にBounded parallelismセクションは興味深いかもしれませんが、必要な正確なパターンを説明しています。

空の構造体のチャネルを制限ガードとして使用して、並行ワーカーgoroutineの数を制御する

package main

import "fmt"

func main() {
    maxGoroutines := 10
    guard := make(chan struct{}, maxGoroutines)

    for i := 0; i < 30; i++ {
        guard <- struct{}{} // would block if guard channel is already filled
        go func(n int) {
            worker(n)
            <-guard
        }(i)
    }
}

func worker(i int) { fmt.Println("doing work on", i) }
39
artyom
  1. データをゴルーチンに渡すためのチャネルを作成します。
  2. チャネル内のデータをループで処理する20個のゴルーチンを開始します。
  3. 新しいgoroutineを開始する代わりに、チャネルにデータを送信します。
14
Grzegorz Żur

ここで私はこのような単純なものがうまくいくと思います:

package main

import "fmt"

const MAX = 20

func main() {
    sem := make(chan int, MAX)
    for {
        sem <- 1 // will block if there is MAX ints in sem
        go func() {
            fmt.Println("hello again, world")
            <-sem // removes an int from sem, allowing another to proceed
        }()
    }
}
12
Emil Davtyan

GrzegorzŻurの answer が最も効率的な方法ですが、初心者にとって、コードを読み取らずに実装するのは難しい場合があるため、次のように非常に単純な実装です。

type idProcessor func(id uint)

func SpawnStuff(limit uint, proc idProcessor) chan<- uint {
    ch := make(chan uint)
    for i := uint(0); i < limit; i++ {
        go func() {
            for {
                id, ok := <-ch
                if !ok {
                    return
                }
                proc(id)
            }
        }()
    }
    return ch
}

func main() {
    runtime.GOMAXPROCS(4)
    var wg sync.WaitGroup //this is just for the demo, otherwise main will return
    fn := func(id uint) {
        fmt.Println(id)
        wg.Done()
    }
    wg.Add(1000)
    ch := SpawnStuff(10, fn)
    for i := uint(0); i < 1000; i++ {
        ch <- i
    }
    close(ch) //should do this to make all the goroutines exit gracefully
    wg.Wait()
}

playground

10
OneOfOne

これは単純な producer-consumer 問題です。Goでは、チャネルを使用してパケットをバッファリングすることで簡単に解決できます。

簡単に言うと、IDを受け入れるチャネルを作成します。ループからチャネルから読み取るルーチンをいくつか実行し、IDを処理します。次に、IDをチャネルにフィードするループを実行します。

例:

func producer() {
    var buffer = make(chan uint)

    for i := 0; i < 20; i++ {
        go consumer(buffer)
    }

    for _, id :=  range IDs {
        buffer <- id
    }
}

func consumer(buffer chan uint) {
    for {
        id := <- buffer
        // Do your things here
    }
}

知っておくべきこと:

  • バッファリングされていないチャネルはブロックしています。チャネルに書き込んだアイテムが受け入れられない場合、アイテムをフィードするルーチンはブロックされるまでブロックされます
  • 私の例には終了メカニズムがありません。プロデューサーがすべてのコンシューマーがループを終了してから戻るまで待機する方法を見つける必要があります。これを行う最も簡単な方法は、別のチャネルを使用することです。考えさせてください。
2
Elwinar