Tunny è una libreria Golang per la creazione e la gestione di pool di goroutine, che ti consente di limitare il lavoro da qualsiasi numero di goroutine utilizzando API sincrone.

Quando il tuo lavoro proviene da un numero arbitrario di fonti asincrone ma la tua capacità di elaborazione in parallelo è limitata, un pool di goroutine fisso è estremamente utile. Ad esempio, quando si elaborano lavori di richieste HTTP intensivi per la CPU, è possibile creare un pool delle dimensioni del numero di CPU.

## Installazione

```shell
go get github.com/Jeffail/tunny

In alternativa, utilizzando dep:

dep ensure -add github.com/Jeffail/tunny

Utilizzo

Per la maggior parte dei casi, il lavoro pesante può essere rappresentato da un semplice func(), nel qual caso è possibile utilizzare NewFunc. Vediamo come utilizzare il nostro esempio di richieste HTTP al conteggio della CPU:

package main

import (
	"io/ioutil"
	"net/http"
	"runtime"

	"github.com/Jeffail/tunny"
)

func main() {
	numCPUs := runtime.NumCPU()

	pool := tunny.NewFunc(numCPUs, func(payload interface{}) interface{} {
		var result []byte

		// TODO: Eseguire alcune operazioni intensive per la CPU utilizzando il payload

		return result
	})
	defer pool.Close()

	http.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) {
		input, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Errore interno", http.StatusInternalServerError)
		}
		defer r.Body.Close()

		// Importare questo lavoro nel nostro pool. Questa chiamata è sincrona e bloccante fino al completamento del lavoro.
		result := pool.Process(input)

		w.Write(result.([]byte))
	})

	http.ListenAndServe(":8080", nil)
}

Tunny supporta anche i timeout. Puoi sostituire la chiamata Process sopra con il seguente codice:

result, err := pool.ProcessTimed(input, time.Second*5)
if err == tunny.ErrJobTimedOut {
	http.Error(w, "Richiesta scaduta", http.StatusRequestTimeout)
}

Puoi anche utilizzare il contesto della richiesta (o qualsiasi altro contesto) per gestire i timeout e le scadenze. Sostituisci semplicemente la chiamata Process con il seguente codice:

result, err := pool.ProcessCtx(r.Context(), input)
if err == context.DeadlineExceeded {
	http.Error(w, "Richiesta scaduta", http.StatusRequestTimeout)
}

Modifica delle dimensioni del pool

Puoi utilizzare SetSize(int) per modificare le dimensioni del pool Tunny in qualsiasi momento.

pool.SetSize(10) // 10 goroutine
pool.SetSize(100) // 100 goroutine

Questo è sicuro anche se altre goroutine stanno ancora elaborando.

Goroutine con stato

A volte, ogni goroutine nel pool Tunny ha bisogno del proprio stato di gestione. In tal caso, è necessario implementare tunny.Worker, che include chiamate per la terminazione, l'interruzione (se un lavoro scade e non è più necessario) e il blocco dell'allocazione del prossimo lavoro fino al verificarsi di una determinata condizione.

Quando si crea un pool con il tipo Worker, è necessario fornire un costruttore per generare la propria implementazione personalizzata:

pool := tunny.New(poolSize, func() Worker {
	// TODO: Eseguire l'allocazione dello stato per ciascuna goroutine qui.
	return newCustomWorker()
})

In questo modo, Tunny può gestire la creazione e la distruzione del tipo Worker quando cambia la dimensione del pool.

Ordinamento

I lavori in coda non sono garantiti di essere elaborati in ordine. A causa dell'attuale implementazione dei canali e dei blocchi select, le pile di lavori in coda verranno elaborare come una coda FIFO. Tuttavia, questo comportamento non fa parte delle specifiche e non dovrebbe essere affidabile.