【问题标题】:Goroutines with ListenAndServe increases performance?带有 ListenAndServe 的 Goroutines 提高了性能?
【发布时间】:2015-08-09 20:21:33
【问题描述】:

我对 Go 的例程不是很熟悉,但由于我使用的是 net/http 的路由器,所以我看到有几次ListenAndServe() 被一个 go 例程包裹。

服务器需要能够开箱即用地同时处理多个请求以提高效率。那么为什么使用 goroutine 作为“轻量级线程”呢? 并发有什么好处吗?

这是OpenShift 的示例

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello OpenShift!")
}

func main() {
    http.HandleFunc("/", helloHandler)

    go func() {
        fmt.Println("serving on 8080")
        err := http.ListenAndServe(":8080", nil)
        if err != nil {
            panic("ListenAndServe: " + err.Error())
        }
    }()

    go func() {
        fmt.Println("serving on 8888")
        err := http.ListenAndServe(":8888", nil)
        if err != nil {
            panic("ListenAndServe: " + err.Error())
        }
    }()
    select {}
}

【问题讨论】:

  • 我相信这样做只是为了让同一个程序可以监听两个端口,而不是为了性能,因为http.ListenAndServe 绑定到单个端口然后阻塞。
  • 好主意,但如果您查看hereherehere,您会发现服务器只侦听单个端口。
  • @Ezra 对列出的示例是正确的,commit log 对此的更改表示“在多个端口上服务”。在您给出的其他每种情况下,这样做的原因都是一样的:对 ListenAndServe 的调用阻塞,如果您想要/需要返回,您需要将其放入 goroutine 中。

标签: concurrency go


【解决方案1】:

http.ListenAndServe 是一个阻塞调用。如果你想做更多的工作(比如第二次调用http.ListenAndServe),你需要把它移到一个单独的goroutine。这就是他们在这里所做的一切。

他们在最后使用select{} 来阻塞主goroutine,因为他们对http.ListenAndServe 的所有调用都在其他goroutine 上。如果他们不调用select{},程序将终止,因为main() 会返回。

他们可以通过删除 select{} 并删除最后一段代码周围的 go func() 包装器来实现相同的目标。但我怀疑他们这样做是为了让所有代码都保持一致。

但这与性能无关。

在 cmets 中,您提供了一些其他类似的示例。在first example

func main() {
    http.HandleFunc("/", responsehandler.Handler)
    go func() {
      http.ListenAndServe(":8888", nil)
    }()
    fileservice.NewWatcher()
}

这会调用http.ListenAndServe,然后调用fileservice.NewWatcher()(阻塞)。如果他们没有将调用封装在 goroutine 中,fileservice.NewWatcher() 将永远不会被调用。

其他 two examples 是常见的样板:

func init() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
}

这将打开调试分析器 Web 服务器。同样,它是一个 goroutine,因此调用 init 会立即返回而不是阻塞。这种特殊情况允许调用者只需 import _ "profiling" 并“神奇地”获取调试分析器 Web 服务器。

【讨论】:

  • 感谢您通过评论示例提供的广泛回答。 Ezra 的假设是正确的。
  • @Rob 这行得通。但是如何优雅地关闭程序。如果我停止运行此代码的程序,则不会出现 goroutine 泄漏。你会如何停止 go func() ??
  • @DineshGowda 当一个进程终止时,所有的 goroutine 都会被自动销毁。这是 Unix 的一个特性; Go 不需要在这里做任何事情(也无法阻止它)。
  • @Weana 我不太确定你的意思。您担心什么并发问题? (处理函数只是函数;如果函数是线程安全的,那么间接调用函数也是安全的。)
  • “当处理函数访问包级(全局)变量时?”如果不使用某种排他访问(锁,另一个 go 例程),处理函数绝不能这样做。这就是我所说的“如果函数是线程安全的”。
【解决方案2】:

不,除了“在后台运行”之外,它没有任何特殊的好处。

【讨论】:

  • “在后台运行”到底是什么意思
  • 在自己的 goroutine 中运行。
【解决方案3】:

我认为您不需要 go 例程来启动 ListenAndServe。根据 Go 文档。

“ListenAndServe 调用 Serve”。发球是常规。

ListenAndServe 侦听 TCP 网络地址 addr,然后使用处理程序调用 Serve 以处理传入连接上的请求。接受的连接配置为启用 TCP 保持连接。 Handler 通常为 nil,在这种情况下使用 DefaultServeMux。 https://golang.org/pkg/net/http/#ListenAndServe

func Serve(l net.Listener, handler Handler) 错误 Serve 在监听器 l 上接受传入的 HTTP 连接,为每个连接创建一个新的服务 goroutine。服务 goroutine 读取请求,然后调用处理程序来回复它们。 Handler 通常为 nil,在这种情况下使用 DefaultServeMux。 https://golang.org/pkg/net/http/#Serve

【讨论】:

  • 没有。该文档讨论了为每个传入连接创建一个 goroutine。 ListenAndServe 仍然是一个阻塞调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-01
  • 2017-01-17
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多