在http处理程序中使用Goroutines进行后台工作

huangapple go评论95阅读模式
英文:

Using Goroutines for background work inside an http handler

问题

我以为我找到了一种简单的方法,可以立即返回一个HTTP响应,然后在后台执行一些工作而不阻塞。然而,这种方法不起作用。

func MyHandler(w http.ResponseWriter, r *http.Request) {
    //处理表单值
    go doSomeBackgroundWork() // 这个工作需要2到3秒钟
    w.WriteHeader(http.StatusOK)
}

第一次运行时,响应会立即返回并且后台工作开始执行。然而,任何后续的请求都会等待后台goroutine完成才会返回。有没有更好的方法来实现这个,而不需要设置消息队列和单独的后台进程呢?

英文:

I thought I'd found an easy way to return an http response immediately then do some work in the background without blocking. However, this doesn't work.

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

It works the first time--the response is returned immediately and the background work starts. However, any further requests hang until the background goroutine completes. Is there a better way to do this, that doesn't involve setting up a message queue and a separate background process.

答案1

得分: 18

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

以下是一种实现方法:

可以使用工作池(worker pool)来实现,参考链接:https://gobyexample.com/worker-pools,使用Go协程和通道。

下面的代码将其适应为一个处理程序(为简单起见,忽略了错误,并且使用了全局变量jobs):

package main

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

var jobs chan int

func worker(jobs <-chan int) {
	fmt.Println("注册工作线程")
	for i := range jobs {
		fmt.Println("工作线程正在处理任务", 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)
}

解释如下:

main()

  • 使用Go协程在后台运行工作线程
  • 使用我的处理程序启动服务
  • 注意,此时工作线程已准备好接收任务

worker()

  • 它是一个接收通道的Go协程
  • for循环永远不会结束,因为通道永远不会关闭
  • 当通道包含一个任务时,执行一些工作(例如等待5秒钟)

handler()

  • 向通道写入以激活一个任务
  • 立即返回并在页面上打印“hello world”

很酷的一点是,你可以发送任意数量的请求,因为这个场景只包含一个工作线程,所以下一个请求将等待前一个请求完成。

这是Go的强大之处!

英文:

I know this question was posted 4 years ago, but I hope someone can find this useful.

Here is a way to do that

There are something called worker pool https://gobyexample.com/worker-pools Using go routines and channels

But in the following code I adapt it to a handler. (Consider for simplicity I'm ignoring the errors and I'm using jobs as global variable)

package main

import (
    &quot;fmt&quot;
    &quot;net/http&quot;
    &quot;time&quot;
)

var jobs chan int

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

func handler(w http.ResponseWriter, r *http.Request) {
    jobs &lt;- 1
    fmt.Fprintln(w, &quot;hello world&quot;)
}

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

    http.HandleFunc(&quot;/request&quot;, handler)
    http.ListenAndServe(&quot;:9090&quot;, nil)
}

The explanation:

main()

  • Runs the worker in background using a go routine
  • Start the service with my handler
  • note that the worker in this moment is ready to receive a job

worker()

  • it is a go routine that receives a channel
  • the for loop never ends because the channel is never closed
  • when a the channel contain a job, do some work (e.g. waits for 5 seconds)

handler()

  • writes to the channel to active a job
  • immediately returns printing "hello world" to the page

the cool thing is that you can send as many requests you want and because this scenario only contains 1 worker. the next request will wait until the previous one finished.

This is awesome Go!

答案2

得分: 14

Go将goroutine复用到可用的线程上,这由GOMAXPROCS环境设置确定。因此,如果将其设置为1,则单个goroutine可以独占Go可用的单个线程,直到它将控制权交还给Go运行时。很可能doSomeBackgroundWork正在独占单个线程的所有时间,这会阻止HTTP处理程序被调度。

有几种方法可以解决这个问题。

首先,通常情况下,在使用goroutine时,应将GOMAXPROCS设置为系统的CPU数量或更大的值。

其次,您可以通过执行以下任何操作之一来让goroutine放弃控制权:

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

所有这些操作都会将控制权交还给Go运行时调度程序,使其他goroutine有机会工作。

英文:

Go multiplexes goroutines onto the available threads which is determined by the GOMAXPROCS environment setting. As a result if this is set to 1 then a single goroutine can hog the single thread Go has available to it until it yields control back to the Go runtime. More than likely doSomeBackgroundWork is hogging all the time on a single thread which is preventing the http handler from getting scheduled.

There are a number of ways to fix this.

First, as a general rule when using goroutines, you should set GOMAXPROCS to the number of CPUs your system has or to whichever is bigger.

Second, you can yield control in a goroutine by doing any of the following:

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

All of these will yield back to the Go runtime scheduler giving other goroutines a chance to work.

huangapple
  • 本文由 发表于 2013年8月18日 04:40:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/18293133.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定