Backend Development 5 min read

Implementing a Dynamic QPS Load‑Testing Model in Go Using a Goroutine Pool

This article presents a Go‑based dynamic QPS load‑testing framework that leverages a custom goroutine pool, explains the rationale behind reusing goroutines, provides core functions for task execution and console command handling, and includes a complete runnable demo illustrating high‑throughput performance testing.

FunTester
FunTester
FunTester
Implementing a Dynamic QPS Load‑Testing Model in Go Using a Goroutine Pool

When the author previously wrote a Kafka client in Go and discussed channel versus Java thread performance with a follower, the need for a systematic way to benchmark the two arose, leading to the development of a dynamic QPS load‑testing model as a foundational capability.

The article references an existing Go goroutine‑pool implementation and adds a method that can execute a given task a specified number of times (QPS) by batching the executions, typically in groups of ten (the default SingleTimes ), to reduce context‑switch overhead.

func (pool *GorotinesPool) ExecuteQps(t func(), qps int) { mutiple := qps / pool.SingleTimes remainder := qps % pool.SingleTimes for i := 0; i < pool.SingleTimes; i++ { pool.Execute(func() { for i := 0; i < mutiple; i++ { t() } }) } pool.Execute(func() { for i := 0; i < remainder; i++ { t() } }) }

The SingleTimes parameter defaults to 10 but can be tuned based on task execution speed, although the author has not yet performed comparative tests.

Control commands are read from the console using a simple loop that reads lines, trims the newline, and passes the input to a handler function.

func HandleInput(handle func(input string) bool) { reader := bufio.NewReader(os.Stdin) for { text, _ := reader.ReadString('\n') text = strings.Replace(text, "\n", "", -1) if handle(text) { break } } }

A demonstration program creates a pool, reads the desired QPS from the console, repeatedly calls ExecuteQps with a logging task, sleeps for a second between iterations, and finally waits for all goroutines to finish.

func main() { pool := execute.GetPool(1000, 2, 200, 1) var qps int = 1 go ftool.HandleInput(func(input string) bool { put, err := strconv.Atoi(input) if err == nil { log.Printf("input content: %d", put) qps = put } return false }) for { pool.ExecuteQps(func() { log.Println(ftool.Date()) }, qps) ftool.Sleep(1000) } pool.Wait() }

The author notes that using main is necessary because Go unit tests cannot handle interactive console input, and observes that Go’s ability to pass functions as parameters feels smoother than Java’s multiple function objects or Groovy’s closures.

backendConcurrencyGoPerformance TestingQPSGoroutine Pool
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.