13

我以为我找到了一种简单的方法来立即返回一个 http 响应,然后在后台做一些工作而不会阻塞。但是,这不起作用。

func MyHandler(w http.ResponseWriter, r *http.Request) {
    //handle form values
    go doSomeBackgroundWork() // this will take 2 or 3 seconds
    w.WriteHeader(http.StatusOK)
}

它第一次工作 - 立即返回响应并开始后台工作。但是,任何进一步的请求都会挂起,直到后台 goroutine 完成。有没有更好的方法来做到这一点,它不涉及设置消息队列和单独的后台进程。

4

2 回答 2

16

我知道这个问题是 4 年前发布的,但我希望有人能发现这很有用。

这是一种方法

有一种叫做工作池的东西https://gobyexample.com/worker-pools使用 go 例程和通道

但在下面的代码中,我将其调整为处理程序。(考虑为简单起见,我忽略了错误,我使用作业作为全局变量)

package main

import (
    "fmt"
    "net/http"
    "time"
)

var jobs chan int

func worker(jobs <-chan int) {
    fmt.Println("Register the worker")
    for i := range jobs {
        fmt.Println("worker processing job", i)
        time.Sleep(time.Second * 5)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    jobs <- 1
    fmt.Fprintln(w, "hello world")
}

func main() {
    jobs = make(chan int, 100)
    go worker(jobs)

    http.HandleFunc("/request", handler)
    http.ListenAndServe(":9090", nil)
}

说明:

主要的()

  • 使用 go 例程在后台运行 worker
  • 使用我的处理程序启动服务
  • 请注意,此时的工人已准备好接受工作

工人()

  • 这是一个接收通道的 goroutine
  • for 循环永远不会结束,因为通道永远不会关闭
  • 当频道包含工作时,做一些工作(例如等待 5 秒)

处理程序()

  • 写入通道以激活作业
  • 立即将打印“hello world”返回到页面

很酷的是,您可以发送任意数量的请求,因为此场景仅包含 1 个工作人员。下一个请求将等到前一个请求完成。

这太棒了!

于 2017-08-31T21:02:58.603 回答
14

GOMAXPROCSGo 将 goroutine 多路复用到由环境设置确定的可用线程上。因此,如果将其设置为 1,那么单个 goroutine 可以占用 Go 对其可用的单个线程,直到它将控制权交还给 Go 运行时。很可能doSomeBackgroundWork一直在单个线程上占用,这会阻止 http 处理程序被调度。

有很多方法可以解决这个问题。

首先,作为使用 goroutine 时的一般规则,您应该设置GOMAXPROCS系统拥有的 CPU 数量或较大的一个。

其次,您可以通过执行以下任何操作来让出 goroutine 中的控制权:

  • runtime.Gosched()
  • ch <- foo
  • foo := <-ch
  • select { ... }
  • mutex.Lock()
  • mutex.Unlock()

所有这些都将返回给 Go 运行时调度程序,让其他 goroutine 有机会工作。

于 2013-08-18T03:32:36.030 回答