Tunnyは、Goroutineプールを作成および管理するためのGolangライブラリであり、同期的なAPIを使用して任意の数のGoroutineからの作業を制限することができます。
あなたの作業が任意の非同期ソースから来るが、並列処理能力が限られている場合、固定されたGoroutineプールは非常に有用です。たとえば、CPU集約型のHTTPリクエストのジョブを処理する場合、CPUの数に合わせたサイズのプールを作成することができます。
インストール
go get github.com/Jeffail/tunny
または、depを使用して:
dep ensure -add github.com/Jeffail/tunny
使用法
ほとんどの場合、重い作業は単純な func() で表現できます。この場合、 NewFunc を使用できます。HTTPリクエストを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: ペイロードを使用してCPU集約型の処理を実行
		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, "Internal error", http.StatusInternalServerError)
		}
		defer r.Body.Close()
		// この作業をプールに取り込みます。この呼び出しは同期的であり、ジョブが完了するまでブロックされます。
		result := pool.Process(input)
		w.Write(result.([]byte))
	})
	http.ListenAndServe(":8080", nil)
}
Tunnyはタイムアウトもサポートしています。上記の Process の呼び出しを以下のコードで置き換えることができます:
result, err := pool.ProcessTimed(input, time.Second*5)
if err == tunny.ErrJobTimedOut {
	http.Error(w, "Request timed out", http.StatusRequestTimeout)
}
また、リクエストコンテキスト(またはその他のコンテキスト)を使用してタイムアウトや締め切りを処理することもできます。次のコードで Process の呼び出しを置き換えるだけです:
result, err := pool.ProcessCtx(r.Context(), input)
if err == context.DeadlineExceeded {
	http.Error(w, "Request timed out", http.StatusRequestTimeout)
}
プールサイズの変更
SetSize(int) を使用して、いつでもTunnyプールのサイズを変更できます。
pool.SetSize(10) // 10 goroutines
pool.SetSize(100) // 100 goroutines
これは、他のgoroutineがまだ処理中であっても安全です。
状態を持つGoroutine
Tunnyプール内の各goroutineが自分自身の管理状態を持つ必要がある場合は、tunny.Workerを実装する必要があります。これには、終了、中断(ジョブがタイムアウトして不要になった場合)、特定の条件が満たされるまで次のジョブの割り当てをブロックするための呼び出しが含まれています。
Workerタイプでプールを作成する場合、カスタム実装を生成するためのコンストラクタを提供する必要があります:
pool := tunny.New(poolSize, func() Worker {
	// TODO:ここで各goroutineのための状態割り当てを実行
	return newCustomWorker()
})
これにより、Tunnyはプールサイズが変更されたときにWorkerタイプの作成および破棄をきれいに行うことができます。
順序付け
バックログのジョブは順序付けられて処理される保証はありません。現在の実装により、バックログのジョブスタックはFIFOキューとして処理されますが、この動作は仕様の一部ではなく、依存すべきではありません。