Eine Sache im Voraus: Wenn Sie einen HTTP-Server betreiben (Go's Standardserver sowieso), können Sie die Anzahl der Goroutines nicht kontrollieren, ohne den Server anzuhalten und neu zu starten. Jede Anfrage startet mindestens eine Goroutine, und Sie können nichts dagegen tun. Die gute Nachricht ist, dass dies normalerweise kein Problem ist, da die Goroutines so leicht sind. Es ist jedoch vollkommen vernünftig, dass Sie die Anzahl der Gorousines, die harte Arbeit leisten, unter Kontrolle halten wollen.
Sie können einen beliebigen Wert einschließlich Funktionen in einen Kanal eingeben. Wenn also das Ziel darin besteht, Code nur in HTTP-Handlern schreiben zu müssen, lassen Sie die Jobs schließen - die Worker wissen nicht (oder interessieren sich nicht), woran sie arbeiten.
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
)
var largePool chan func()
var smallPool chan func()
func main() {
// Start two different sized worker pools (e.g., for different workloads).
// Cancelation and graceful shutdown omited for brevity.
largePool = make(chan func(), 100)
smallPool = make(chan func(), 10)
for i := 0; i < 100; i++ {
go func() {
for f := range largePool {
f()
}
}()
}
for i := 0; i < 10; i++ {
go func() {
for f := range smallPool {
f()
}
}()
}
http.HandleFunc("/endpoint-1", handler1)
http.HandleFunc("/endpoint-2", handler2) // naming things is hard, okay?
http.ListenAndServe(":8080", nil)
}
func handler1(w http.ResponseWriter, r *http.Request) {
// Imagine a JSON body containing a URL that we are expected to fetch.
// Light work that doesn't consume many of *our* resources and can be done
// in bulk, so we put in in the large pool.
var job struct{ URL string }
if err := json.NewDecoder(r.Body).Decode(&job); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
go func() {
largePool <- func() {
http.Get(job.URL)
// Do something with the response
}
}()
w.WriteHeader(http.StatusAccepted)
}
func handler2(w http.ResponseWriter, r *http.Request) {
// The request body is an image that we want to do some fancy processing
// on. That's hard work; we don't want to do too many of them at once, so
// so we put those jobs in the small pool.
b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go func() {
smallPool <- func() {
processImage(b)
}
}()
w.WriteHeader(http.StatusAccepted)
}
func processImage(b []byte) {}
Dies ist ein sehr einfaches Beispiel, um den Punkt zu vermitteln. Es spielt keine Rolle, wie Sie Ihre Worker-Pools einrichten. Sie brauchen nur eine clevere Jobdefinition.Im obigen Beispiel handelt es sich um eine Schließung, aber Sie könnten beispielsweise auch eine Job-Schnittstelle definieren.
type Job interface {
Do()
}
var largePool chan Job
var smallPool chan Job
Nun, ich würde nicht den ganzen Arbeiter Pool Ansatz "einfache" nennen. Du hast gesagt, dein Ziel ist es, die Anzahl der Goroutines (die arbeiten) zu begrenzen. Das erfordert keine Arbeiter überhaupt; es braucht nur einen Begrenzer. Hier ist das gleiche Beispiel wie oben, aber die Verwendung von Kanälen als Semaphore zur Begrenzung der Nebenläufigkeit.
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
)
var largePool chan struct{}
var smallPool chan struct{}
func main() {
largePool = make(chan struct{}, 100)
smallPool = make(chan struct{}, 10)
http.HandleFunc("/endpoint-1", handler1)
http.HandleFunc("/endpoint-2", handler2)
http.ListenAndServe(":8080", nil)
}
func handler1(w http.ResponseWriter, r *http.Request) {
var job struct{ URL string }
if err := json.NewDecoder(r.Body).Decode(&job); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
go func() {
// Block until there are fewer than cap(largePool) light-work
// goroutines running.
largePool <- struct{}{}
defer func() { <-largePool }() // Let everyone that we are done
http.Get(job.URL)
}()
w.WriteHeader(http.StatusAccepted)
}
func handler2(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
go func() {
// Block until there are fewer than cap(smallPool) hard-work
// goroutines running.
smallPool <- struct{}{}
defer func() { <-smallPool }() // Let everyone that we are done
processImage(b)
}()
w.WriteHeader(http.StatusAccepted)
}
func processImage(b []byte) {}
Go's http-Paket startet eine Go-Routine für jede eingehende Verbindung. Wenn Sie nicht über die Verarbeitung von Hintergrundjobs sprechen, scheint dies eine verschwendete Anstrengung zu sein. – squiguy
Ja, das ist korrekt für die Hintergrundverarbeitung. Einige, die eine Weile dauern könnten, um zu beenden, und ich lasse eher eine unkontrollierte Menge von Goroutines los –
Was ist das Problem mit Göroutinen? Sie sind im Grunde die eingebaute Implementierung von Jobwarteschlangen mit asynchroner Unterstützung. –