Using Golang channels to handle HTTP requests

2019-04-16 15:00发布

I'm trying to build a simple Golang/Appengine app which uses a channel to handle each http request. Reason is I want each request to perform a reasonable large in- memory calculation, and it's important that each request is performed in a thread- safe manner (ie calculations from concurrent requests don't get mixed).

Essentially I need a synchronous queue which will only process one request at a time, and channels look like a natural fit.

Is it possible to use Go's buffered channel as a thread-safe queue?

However I can't get my simple hello world example to work. It seems to fail on the line 'go process(w, cr)'; I get a 200 response from the server, but no contennt. Works fine if I eliminate 'go' from the this line, but then I'm guessing I'm not calling the channel correctly.

Anyone point out where I'm going wrong ?

Thanks!

// curl -X POST "http://localhost:8080/add" -d "{\"A\":1, \"B\":2}"

package hello

import (
    "encoding/json"
    "net/http"  
)

type MyStruct struct {
    A, B, Total int64
}

func (s *MyStruct) add() {
    s.Total = s.A + s.B
}

func process(w http.ResponseWriter, cr chan *http.Request) {
    r := <- cr
    var s MyStruct
    json.NewDecoder(r.Body).Decode(&s)
    s.add()
    json.NewEncoder(w).Encode(s)
}

func handler(w http.ResponseWriter, r *http.Request) {  
    cr := make(chan *http.Request, 1)
    cr <- r
    go process(w, cr) // doesn't work; no response :-(
    // process(w, cr) // works, but blank response :-(
}

func init() {
    http.HandleFunc("/add", handler)
}

2条回答
时光不老,我们不散
2楼-- · 2019-04-16 15:14

If the large computation does not use shared mutable state, then write a plain handler. There's no need for channels and what not.

OK, the large computation does use shared mutable state. If there's only one instance of the application running, then use sync.Mutex to control access to the mutable state. This is simple compared to shuffling the work off to single goroutine to process the computations one at a time.

Are you running on App Engine? You might not be able to guarantee that there's a single instance of the application running. You will need to use the datastore or memcache for mutable state. If the computation can be done offline (after the request completes), then you can use App Engine Task Queues to process the computations one at a time.

A side note: The title proposes a solution to the problem stated in the body of the question. It would be better to state the problem directly. I would comment above on this, but I don't have the juice required.

查看更多
手持菜刀,她持情操
3楼-- · 2019-04-16 15:19

Not sure this is the right design but I suspect that the issue is that where you're starting the second go routine the first go routine continues and finishes writing the connection etc.

To stop this you can make the first routine wait using a waitgroup (http://golang.org/pkg/sync/#WaitGroup).

This stop the whole reasoning behind why you're trying to put this into a thread (hence why I think you've got a design issue).

Here is some untested code that should work or at least help in the right direction.

package main

import (
    "encoding/json"
    "net/http"
    "sync"  
)

type MyStruct struct {
    A, B, Total int64
}

func (s *MyStruct) add() {
    s.Total = s.A + s.B
}

func process(w http.ResponseWriter, cr chan *http.Request) {
    r := <- cr
    var s MyStruct
    json.NewDecoder(r.Body).Decode(&s)
    s.add()
    json.NewEncoder(w).Encode(s)
}

func handler(w http.ResponseWriter, r *http.Request) {  
    cr := make(chan *http.Request, 1)
    cr <- r
    var pleasewait sync.WaitGroup
    pleasewait.Add(1)

    go func() {
        defer pleasewait.Done()
        process(w, cr) // doesn't work; no response :-(
    }()
    // process(w, cr) // works, but blank response :-(

    pleasewait.Wait()
}

func main() {
    http.HandleFunc("/add", handler)
}
查看更多
登录 后发表回答