Tunny é uma biblioteca Golang para criar e gerenciar pools de goroutines, permitindo limitar o trabalho a partir de qualquer número de goroutines usando APIs síncronas.
Quando seu trabalho vem de um número arbitrário de fontes assíncronas, mas sua capacidade de processamento paralelo é limitada, um pool fixo de goroutines é extremamente útil. Por exemplo, ao processar trabalhos de solicitações HTTP intensivas em CPU, você pode criar um pool do tamanho do número de CPUs.
Instalação
go get github.com/Jeffail/tunny
Alternativamente, usando dep:
dep ensure -add github.com/Jeffail/tunny
Uso
Para a maioria dos casos, seu trabalho pesado pode ser representado por um simples func()
, caso em que você pode usar NewFunc
. Vamos ver como usar nosso exemplo de contagem de CPU para solicitações HTTP:
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: Realizar algumas operações intensivas em CPU usando 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, "Erro interno", http.StatusInternalServerError)
}
defer r.Body.Close()
// Importar este trabalho para o nosso pool. Esta chamada é síncrona e bloqueará até que o trabalho esteja completo.
result := pool.Process(input)
w.Write(result.([]byte))
})
http.ListenAndServe(":8080", nil)
}
Tunny também suporta timeouts. Você pode substituir a chamada Process
acima pelo seguinte código:
result, err := pool.ProcessTimed(input, time.Second*5)
if err == tunny.ErrJobTimedOut {
http.Error(w, "Requisição expirou", http.StatusRequestTimeout)
}
Você também pode usar o contexto da solicitação (ou qualquer outro contexto) para lidar com timeouts e prazos. Basta substituir a chamada Process
pelo seguinte código:
result, err := pool.ProcessCtx(r.Context(), input)
if err == context.DeadlineExceeded {
http.Error(w, "Requisição expirou", http.StatusRequestTimeout)
}
Modificando o Tamanho do Pool
Você pode usar SetSize(int)
para mudar o tamanho do pool Tunny a qualquer momento.
pool.SetSize(10) // 10 goroutines
pool.SetSize(100) // 100 goroutines
Isso é seguro mesmo que outras goroutines ainda estejam processando.
Goroutine com Estado
Às vezes, cada goroutine no pool Tunny precisa de seu próprio estado de gerenciamento. Nesse caso, você deve implementar tunny.Worker
, que inclui chamadas para término, interrupção (se um trabalho expira e não é mais necessário) e bloqueio da alocação do próximo trabalho até que uma certa condição seja atendida.
Ao criar um pool com o tipo Worker
, você precisa fornecer um construtor para gerar sua implementação personalizada:
pool := tunny.New(poolSize, func() Worker {
// TODO: Realizar alocação de estado para cada goroutine aqui.
return newCustomWorker()
})
Dessa forma, Tunny pode limpar a criação e destruição do tipo Worker
quando o tamanho do pool muda.
Ordenação
Trabalhos em backlog não têm garantia de serem processados na ordem. Devido à implementação atual de canais e blocos de seleção, as pilhas de trabalhos em backlog serão processadas como uma fila FIFO. No entanto, esse comportamento não faz parte da especificação e não deve ser confiado.