2017-12-24 29 views
0

Ich baue eine Golang App, die einen "Worker Pool" von Goroutines verwendet, anfänglich starte ich den Pool und erstelle eine Anzahl von Arbeitern. Ich habe mich gefragt, wie die optimale Anzahl von Arbeitern in einem Multi-Core-Prozessor wäre, zum Beispiel in einer CPU mit 4 Kernen? Ich verwende derzeit die folgenden aproach:optimale Größe von Golang Worker Pool

// init pool 
    numCPUs := runtime.NumCPU() 

    runtime.GOMAXPROCS(numCPUs + 1) // numCPUs hot threads + one for async tasks. 
    maxWorkers := numCPUs * 4 

    jobQueue := make(chan job.Job) 

    module := Module{ 
     Dispatcher: job.NewWorkerPool(maxWorkers), 
     JobQueue: jobQueue, 
     Router:  router, 
    } 

    // A buffered channel that we can send work requests on. 
    module.Dispatcher.Run(jobQueue) 

Die vollständige Implementierung ist unter

job.NewWorkerPool (maxWorkers) und module.Dispatcher.Run (Jobqueue)

Mein Anwendungsfall für die Verwendung eines Worker-Pools: Ich habe einen Dienst, der Anfragen akzeptiert und mehrere externe APIs aufruft und deren Ergebnisse in einer einzigen Antwort aggregiert. Jeder Aufruf kann unabhängig von anderen erfolgen, da die Reihenfolge der Ergebnisse keine Rolle spielt. Ich gebe die Aufrufe an den Worker-Pool ab, wo jeder Aufruf in einer verfügbaren Goroutine asynchron erfolgt. Mein Thread "request" überwacht weiterhin die Rückkanäle, während Ergebnisse abgerufen und aggregiert werden, sobald ein Worker-Thread ausgeführt wird. Wenn alle fertig sind, wird das endgültige aggregierte Ergebnis als Antwort zurückgegeben. Da jeder externe API-Aufruf variable Antwortzeiten darstellen kann, können einige Aufrufe früher als andere beendet werden. Nach meinem Verständnis wäre es in Bezug auf die Leistung besser, als ob es im Vergleich zu synchronen Aufruf jeder externen API hintereinander wäre.

+0

Es gibt keine objektive Antwort darauf. Es hängt ganz davon ab, was Sie mit Ihren Mitarbeitern machen und welche Leistungsmerkmale Sie anstreben. Wenn Ihre Mitarbeiter E/A-gebunden sind (z. B. ein HTTP- oder SMTP-Server), könnten Sie möglicherweise 1.000 Mitarbeiter auf einem einzelnen CPU-Rechner behandeln. Wenn sie CPU-gebunden sind, sehen Sie möglicherweise keinen Vorteil mehr als einen Arbeiter pro CPU. – Flimzy

+1

Es gibt auch keinen Grund, 'GOMAXPROCS' in 99,99% der modernen Go-Anwendungen zu setzen. Und es auf NUM CPUs + 1 zu setzen macht nie Sinn, AFAIK. Sie sollten dies nur festlegen, wenn Sie den Standardwert reduzieren möchten. – Flimzy

+1

Es hängt davon ab, welche Art von Problem Sie lösen (CPU-gebunden oder anders), und das Leben jeder Ihrer Aufgaben, die der Worker-Thread behandeln wird. Sie müssen ein paar Zahlen ausprobieren, Benchmarks und sehen, was am besten für Ihren Anwendungsfall funktioniert. – Ravi

Antwort

4

Die Kommentare in Ihrem Beispielcode deuten darauf hin, dass Sie die zwei Konzepte von GOMAXPROCS und ein Worker-Pool. Diese beiden Konzepte sind in Go völlig verschieden.

  1. GOMAXPROCS legt die maximale Anzahl von CPU-Threads die Go-Laufzeit verwenden. Dies ist standardmäßig die Anzahl der im System gefundenen CPU-Kerne und sollte fast nie geändert werden. Die einzige Zeit, die ich mir vorstellen könnte, dies zu ändern, wäre, wenn Sie ein Go-Programm explizit einschränken möchten, um aus irgendeinem Grund weniger als die verfügbaren CPUs zu verwenden, dann können Sie dies beispielsweise auf 1 setzen. Kern-CPU. Dies sollte nur in seltenen Situationen von Bedeutung sein.

    TL; DR; Niemals runtime.GOMAXPROCS manuell einstellen.

  2. Worker-Pools in Go sind eine Reihe von Goroutines, die bei ihrer Ankunft Jobs erledigen. Es gibt verschiedene Möglichkeiten, Worker-Pools in Go zu bearbeiten.

    Welche Anzahl von Arbeitern sollten Sie verwenden? Es gibt keine objektive Antwort. Die einzige Möglichkeit, dies zu wissen, ist, verschiedene Konfigurationen zu vergleichen, bis Sie eine finden, die Ihren Anforderungen entspricht.

    Nehmen wir einmal an, dass Ihr Worker-Pool etwas sehr CPU-intensives tut. In diesem Fall möchten Sie wahrscheinlich einen Arbeiter pro CPU haben.

    Als ein wahrscheinlicheres Beispiel jedoch sagen wir, dass Ihre Mitarbeiter etwas mehr I/O-gebunden sind - wie das Lesen von HTTP-Anfragen oder das Senden von E-Mails über SMTP. In diesem Fall können Sie vernünftig mit Dutzenden oder sogar Tausenden von Arbeitern pro CPU umgehen.

    Und dann gibt es auch die Frage, ob Sie sogar einen Worker Pool verwenden sollten. Die meisten Probleme in Go erfordern keine Worker-Pools. Ich habe an Dutzenden von Go-Produktionsprogrammen gearbeitet und nie einen Arbeitspool in einem von ihnen verwendet. Ich habe auch schon öfter einmal Go-Tools verwendet und nur einmal einen Worker-Pool verwendet.

Und schließlich der einzige Weg, in der GOMAXPROCS und Arbeitern Pools beziehen ist das gleiche wie wie goroutines zu GOMAXPROCS bezieht. Von :

Die Variable GOMAXPROCS begrenzt die Anzahl der Betriebssystem-Threads, die gleichzeitig Go-Code auf Benutzerebene ausführen können. Es gibt keine Begrenzung für die Anzahl der Threads, die bei Systemaufrufen für Go-Code blockiert werden können. diese zählen nicht zum GOMAXPROCS-Limit. Die GOMAXPROCS-Funktion dieses Pakets fragt ab und ändert das Limit. GOMAXPROCS nur Grenzen, wie viele „Betriebssystem-Threads, die Benutzer ausführen kann - viel mehr sein könnte (möglicherweise Hunderttausende ... oder mehr) goroutines als GOMAXPROCS

Aus dieser einfachen Beschreibung ist es einfach, dass es zu sehen -level Go code simultan "- goroutines, die momentan keinen Go-Code auf Benutzerebene ausführen, zählen nicht. Und in I/O-gebundenen goroutines (wie diejenigen, die auf eine Netzwerkantwort warten) führen Code nicht aus. Sie haben also eine theoretische maximale Anzahl von Gououtines, die nur durch den verfügbaren Speicher Ihres Systems begrenzt ist.

+0

Mein Anwendungsfall für die Verwendung eines Worker-Pools ist, wenn ich mehrere externe APIs habe, die ich aufrufen und aggregieren muss, da die Reihenfolge keine Rolle spielt, lasse ich jeden Aufruf parallel abwickeln und die Ergebnisse aggregieren es wird ein Worker-Thread ausgeführt, anstatt jeden Aufruf nacheinander auszuführen, so wie es gemacht würde, wenn ich nur einen einzelnen Thread in einer synchronen Weise verwenden würde. – guilhebl

+0

"Mein Code implementiert wirklich einen Worker-Pool" - ehrlich gesagt, ich entschuldige mich dafür, dass ich das übersehen habe. Ich habe diesen Kommentar aus meiner Antwort entfernt. Ich glaube, der Rest der Antwort gilt. – Flimzy

+1

@guilhebl die Alternative ist nicht "nur einen einzelnen Thread in einer synchronen Weise verwenden". Die Alternative besteht darin, unpooled Worker zu verwenden - eine Gorroutine pro Aufgabe - und sich vom Scheduler kümmern zu lassen. Dies ist die meiste Zeit die richtige Lösung, wie die Antwort erklärt. – Adrian

Verwandte Themen