【问题标题】:Goroutines broke the programGoroutines 破坏了程序
【发布时间】:2013-08-04 03:38:21
【问题描述】:

问题是这样的:有一个网络服务器。我认为在页面加载中使用 goroutine 会很有好处,所以我继续做了:调用 loadPage 函数作为 goroutine。但是,执行此操作时,服务器会停止工作而不会出现错误。它打印一个空白的白页。问题必须出在函数本身——某些东西与 goroutine 有某种冲突。

这些是相关的功能:

func loadPage(w http.ResponseWriter, path string) {
   s := GetFileContent(path)
   w.Header().Add("Content-Type", getHeader(path))
   w.Header().Add("Content-Length", GetContentLength(path))
   fmt.Fprint(w, s)
}
func GetFileContent(path string) string {
   cont, err := ioutil.ReadFile(path)
   e(err)
   aob := len(cont)
   s := string(cont[:aob])
   return s
}


func GetFileContent(path string) string {
   cont, err := ioutil.ReadFile(path)
   e(err)
   aob := len(cont)
   s := string(cont[:aob])
   return s
}

func getHeader(path string) string {
   images := []string{".jpg", ".jpeg", ".gif", ".png"}
   readable := []string{".htm", ".html", ".php", ".asp", ".js", ".css"}
   if ArrayContainsSuffix(images, path) {
      return "image/jpeg"
   }
   if ArrayContainsSuffix(readable, path) {
      return "text/html"
   }
   return "file/downloadable"
}


func ArrayContainsSuffix(arr []string, c string) bool {
   length := len(arr)
   for i := 0; i < length; i++ {
      s := arr[i]
      if strings.HasSuffix(c, s) {
         return true
      }
   }
return false
}

【问题讨论】:

  • 我看不出这些功能有什么问题。 Goroutines 就像守护线程,如果主 goroutine 退出,所有当前的 goroutines 都被杀死而没有完成。检查以确保您的主 goroutine 在此完成之前不会退出。
  • 是的,这是件好事。此外,您是否尝试过不同时运行它们时会发生什么?一切正常吗?可能问题实际上并不在于在 goroutine 中运行。

标签: go goroutine


【解决方案1】:

之所以会这样,是因为你调用“loadPage”的HandlerFunc是与请求同步调用的。当您在 go 例程中调用它时,处理程序实际上是立即返回,从而导致立即发送响应。这就是你得到一个空白页的原因。

您可以在server.go(第 1096 行)中看到这一点:

serverHandler{c.server}.ServeHTTP(w, w.req)
if c.hijacked() {
    return
}
w.finishRequest()

ServeHTTP 函数调用您的处理程序,并在它返回后立即调用“finishRequest”。所以你的 Handler 函数必须阻塞,只要它想满足请求。

使用 go 例程实际上不会使您的页面更快。正如 Philip 建议的那样,将单个 go 例程与通道同步在这种情况下也对您没有帮助,因为这与根本没有 go 例程是一样的。

你问题的根源其实是ioutil.ReadFile,它在发送之前将整个文件缓冲到内存中。

如果你想流式传输你需要使用os.Open 的文件。您可以使用io.Copy 将文件内容流式传输到浏览器,浏览器将使用分块编码。

看起来像这样:

f, err := os.Open(path)
if err != nil {
    http.Error(w, "Not Found", http.StatusNotFound)
    return
}
n, err := io.Copy(w, f)
if n == 0 && err != nil {
    http.Error(w, "Error", http.StatusInternalServerError)
    return
}

如果由于某种原因您需要在多个 go 例程中工作,请查看 sync.WaitGroup。频道也可以工作。

如果您只想提供一个文件,还有其他为此优化的选项,例如FileServerServeFile

【讨论】:

  • 这是一个很好的回应,谢谢。很好的澄清..我们做这样的事情(即readfile等)的原因是因为我们基本上刚刚开始这个项目来学习golang,所以它肯定不是以最好的方式完成的。谢谢!
  • @Arcticcu 在这种情况下,我强烈建议您阅读内置包的源代码。当文档没有帮助时,还要查找以“_test.go”结尾的所有文件。
【解决方案2】:

在 Go 中的典型 Web 框架实现中,路由处理程序作为 Goroutines 调用。 IE。在某些时候,Web 框架会显示 go loadPage(...)

因此,如果您从内部 loadPage 调用 Go 例程,则您有 两个 级别的 Goroutines。

Go 调度器真的很懒惰,如果不是强制执行,它不会执行第二级。所以你需要通过同步事件来强制执行它。例如。通过使用频道或sync 包。示例:

func loadPage(w http.ResponseWriter, path string) {
  s := make(chan string)
  go GetFileContent(path, s)
  fmt.Fprint(w, <-s)
}

Go documentation 是这样说的:

如果一个goroutine的效果必须被另一个goroutine观察, 使用同步机制,例如锁或通道 建立相对顺序的通信。

为什么这实际上是一件聪明的事情?在大型项目中,您可能会处理大量需要以某种方式有效协调的 Goroutine。那么,如果 Goroutine 的输出无处使用,为什么还要调用它呢?一个有趣的事实:像 fmt.Printf 这样的 I/O 操作也会触发同步事件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-05
    • 1970-01-01
    • 2016-10-03
    • 2016-05-16
    • 2012-11-26
    • 2018-03-31
    • 2021-07-14
    • 2012-04-03
    相关资源
    最近更新 更多