这个程序有什么问题?

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,特别是刚开始时。与生活中的所有事情一样,适度是成功的关键。

最后编辑: kuteng  文档更新时间: 2021-01-09 21:53   作者:kuteng