Tunny to biblioteka Golang do tworzenia i zarządzania pulami gorutyn, pozwalająca na ograniczenie pracy z dowolną liczbą gorutyn przy użyciu synchronicznych interfejsów.

Kiedy praca pochodzi z dowolnej liczby asynchronicznych źródeł, a zdolność do przetwarzania równoległego jest ograniczona, stała pula gorutyn jest niezwykle przydatna. Na przykład, podczas przetwarzania żądań HTTP obciążających procesor, można utworzyć pulę o wielkości odpowiadającej liczbie procesorów.

## Instalacja

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

Alternatywnie, przy użyciu dep:

dep ensure -add github.com/Jeffail/tunny

Użycie

W większości przypadków, ciężką pracę można przedstawić za pomocą prostego func(), wtedy można użyć NewFunc. Zobaczmy, jak użyć naszego przykładu żądań HTTP do zliczania 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: Wykonaj operacje obciążające procesor przy użyciu 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, "Błąd wewnętrzny", http.StatusInternalServerError)
		}
		defer r.Body.Close()

		// Przenieś tę pracę do naszej puli. To wywołanie jest synchroniczne i zablokuje się aż praca zostanie ukończona.
		result := pool.Process(input)

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

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

Tunny obsługuje także limity czasu. Możesz zastąpić powyższe wywołanie Process kodem:

result, err := pool.ProcessTimed(input, time.Second*5)
if err == tunny.ErrJobTimedOut {
	http.Error(w, "Upłynął czas żądania", http.StatusRequestTimeout)
}

Możesz również użyć kontekstu żądania (lub dowolnego innego kontekstu) do obsługi limitów czasowych i terminów. Wystarczy zastąpić wywołanie Process kodem:

result, err := pool.ProcessCtx(r.Context(), input)
if err == context.DeadlineExceeded {
	http.Error(w, "Upłynął czas żądania", http.StatusRequestTimeout)
}

Modyfikowanie wielkości puli

Możesz użyć SetSize(int), aby dowolnie zmieniać wielkość puli Tunny.

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

Jest to bezpieczne, nawet jeśli inne gorutyny nadal są przetwarzane.

Gorutyna z zachowaniem stanu

Czasami, każda gorutyna w puli Tunny potrzebuje własnego stanu zarządzania. W takim przypadku powinieneś zaimplementować tunny.Worker, który obejmuje wywołania do zakończenia, przerwania (jeśli praca przekroczy limit czasu i nie jest już potrzebna) oraz blokowania przydziału kolejnej pracy, aż do spełnienia określonego warunku.

Przy tworzeniu puli z typem Worker musisz dostarczyć konstruktor do generowania własnej implementacji:

pool := tunny.New(poolSize, func() Worker {
	// TODO: Wykonaj przydział stanu dla każdej gorutyny tutaj.
	return newCustomWorker()
})

W ten sposób Tunny może poradzić sobie z tworzeniem i niszczeniem typu Worker przy zmianie wielkości puli.

Kolejkowanie

Przesunięte prace nie są gwarantowane do przetwarzania w kolejności. Ze względu na bieżącą implementację kanałów i bloki wyboru, zaległe stosy zadań zostaną przetworzone jako kolejka FIFO. Jednak zachowanie to nie jest częścią specyfikacji i nie powinno się na nim polegać.