Why is there such a delay in processing of incoming requests by the main server goroutine and how can this delay be avoided ?
Simple code with NO DELAYS
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", root)
http.ListenAndServe(":8090", nil)
}
//---------------------------------------------------------------------------
// http handlers
//---------------------------------------------------------------------------
func root(w http.ResponseWriter, r *http.Request) {
log.Printf("[root] `%v`
", r.URL.Path)
w.Write([]byte("What the hell"))
}
Result testing the loading
╰─➤ wrk -d20s -t5 -c100 http://localhost:8090
Running 20s test @ http://localhost:8090
5 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 17.07ms 46.63ms 368.77ms 91.73%
Req/Sec 10.99k 4.87k 24.48k 62.49%
1038912 requests in 20.10s, 128.80MB read
Requests/sec: 51684.63
Transfer/sec: 6.41MB
Adding goroutines
package main
import (
"log"
"net/http"
)
func main() {
_ = NewTesterGo(100)
http.HandleFunc("/", root)
http.ListenAndServe(":8090", nil)
}
//---------------------------------------------------------------------------
// http handlers
//---------------------------------------------------------------------------
func root(w http.ResponseWriter, r *http.Request) {
log.Printf("[root] `%v`
", r.URL.Path)
w.Write([]byte("What the fuck"))
}
//---------------------------------------------------------------------------
// tester segment
//---------------------------------------------------------------------------
type (
TesterGo struct {
Work chan string
}
)
func NewTesterGo(count int) *TesterGo {
t:=&TesterGo{
Work:make(chan string,100),
}
for ; count > 0 ; count -- {
go t.Worker()
}
return t
}
func (t *TesterGo) Worker() {
log.Printf("[testergo][worker][work] стартовал....
")
for {
select {
case work := <-t.Work:
log.Printf("[testerGo][Worker] %v
", work)
default:
}
}
}
Result with loading
╰─➤ wrk -d20s -t5 -c100 http://localhost:8090
Running 20s test @ http://localhost:8090
5 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 464.71ms 305.44ms 1.90s 77.90%
Req/Sec 54.62 43.74 200.00 67.50%
3672 requests in 20.05s, 466.17KB read
Socket errors: connect 0, read 0, write 0, **timeout 97**
Requests/sec: **183.11**
Transfer/sec: 23.25KB
your goroutines use default
, causing them to spin immediately if there is nothing in the channel (and there is nothing in your example). This probably makes Go's scheduler do way more context switching than needed, and probably consume a lot of CPU for nothing.
Is there a reason to default
in a loop? If not try one of the following:
Either no default, the goroutines would simply "sleep" until there's work.
for {
select {
case work := <-t.Work:
log.Printf("[testerGo][Worker] %v
", work)
}
}
This BTW makes the select completely redundant, so just get rid of it:
for { //you can also use a range on the channel
work := <- t.Work
log.Printf("[testerGo][Worker] %v
", work)
}
Second option - a timeout that will make them wait before continuing in the loop:
for {
select {
case work := <-t.Work:
log.Printf("[testerGo][Worker] %v
", work)
case <- time.After(100*time.Millisecond): //or whatever you prefer
}
}