Je vois beaucoup de tutoriels et d'exemples sur la façon de faire aller Go jusqu'à ce que x nombre de goroutines se terminent, mais ce que j'essaie de faire est de m'assurer qu'il y a toujours x nombre en cours d'exécution, donc un nouveau goroutine est lancé dès que l'on se termine .
Plus précisément, j'ai quelques centaines de milliers de "choses à faire" qui traitent des trucs qui sortent de MySQL. Donc ça marche comme ça:
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()
Actuellement, cela lancera plusieurs centaines de milliers de threads de processTheThing()
. Ce dont j'ai besoin, c'est qu'un maximum de x (nous l'appellerons 20) goroutines sont lancés. Il commence donc par lancer 20 pour les 20 premières lignes, et à partir de là, il lancera un nouveau goroutine pour le prochain identifiant au moment où l'un des goroutines actuels est terminé. Donc à tout moment, il y en a toujours 20 en cours d'exécution.
Je suis sûr que c'est assez simple/standard, mais je n'arrive pas à trouver une bonne explication sur l'un des tutoriels ou des exemples ou comment cela est fait.
Merci à tous de m'avoir aidé. Cependant, je ne pense pas que quiconque ait vraiment fourni quelque chose à la fois efficace et simple/compréhensible, même si vous m'avez tous aidé à comprendre la technique.
Ce que j'ai fait à la fin, c'est que je pense que c'est beaucoup plus compréhensible et pratique comme réponse à ma question spécifique, donc je vais le poster ici au cas où quelqu'un d'autre aurait la même question.
D'une manière ou d'une autre, cela a fini par ressembler beaucoup à ce que OneOfOne a publié, ce qui est génial parce que maintenant je comprends cela. Mais le code de OneOfOne que j'ai trouvé très difficile à comprendre au début en raison du passage des fonctions aux fonctions, il était assez déroutant de comprendre quel bit était pour quoi. Je pense que cette façon a beaucoup plus de sens:
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
}
Vous pouvez trouver Go Concurrency Patterns article intéressant, en particulier section Parallélisme borné , il explique le modèle exact dont vous avez besoin.
Vous pouvez utiliser canal de structures vides comme garde limite pour contrôler le nombre de goroutines de travailleurs simultanés :
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) }
Ici, je pense que quelque chose de simple comme celui-ci fonctionnera:
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
}()
}
}
Grzegorz Żur's answer est le moyen le plus efficace de le faire, mais pour un nouveau venu, il pourrait être difficile de l'implémenter sans lire le code, alors voici une implémentation très simple:
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()
}
Il s'agit d'un simple problème producteur-consommateur , qui dans Go peut être facilement résolu en utilisant des canaux pour tamponner les paquets.
Pour faire simple: créez une chaîne qui accepte vos identifiants. Exécutez un certain nombre de routines qui liront le canal dans une boucle, puis traiter l'ID. Exécutez ensuite votre boucle qui fournira des identifiants au canal.
Exemple:
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
}
}
À savoir: