这个程序有什么问题?
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, GopherCon SG")
})
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}()
for {
}
}
该程序实现了我们的预期,它提供简单的 Web 服务。 然而,它同时也做了其他事情,它在无限循环中浪费 CPU 资源。 这是因为 main
的最后一行上的 for {}
将阻塞 main goroutine
,因为它不执行任何 IO、等待锁定、发送或接收通道数据或以其他方式与调度器通信。
由于 Go 语言运行时主要是协同调度,该程序将在单个 CPU 上做无效地旋转,并可能最终实时锁定。
我们如何解决这个问题? 这是一个建议。
package main
import (
"fmt"
"log"
"net/http"
"runtime"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, GopherCon SG")
})
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}()
for {
runtime.Gosched()
}
}
这看起来很愚蠢,但这是我看过的一种常见解决方案。 这是不了解潜在问题的症状。
现在,如果你有更多的经验,你可能会写这样的东西。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, GopherCon SG")
})
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}()
select {}
}
空的 select
语句将永远阻塞。 这是一个有用的属性,因为现在我们不再调用 runtime.GoSched()
而耗费整个 CPU。 但是这也只是治疗了症状,而不是病根。
我想向你提出另一种你可能在用的解决方案。 与其在 goroutine
中运行 http.ListenAndServe
,会给我们留下处理 main goroutine
的问题,不如在 main goroutine
本身上运行 http.ListenAndServe
。
贴士:
如果 Go 语言程序的main.main
函数返回,无论程序在一段时间内启动的其他goroutine
在做什么, Go 语言程序会无条件地退出。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, GopherCon SG")
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
所以这是我的第一条建议:如果你的 goroutine
在得到另一个结果之前无法取得进展,那么让自己完成此工作而不是委托给其他 goroutine
会更简单。
这通常会消除将结果从 goroutine
返回到其启动程序所需的大量状态跟踪和通道操作。
贴士:
许多 Go 程序员过度使用goroutine
,特别是刚开始时。与生活中的所有事情一样,适度是成功的关键。