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ć.