英文:
Using Golang channels to handle HTTP requests
问题
我正在尝试构建一个简单的 Golang/Appengine 应用程序,该应用程序使用通道来处理每个 HTTP 请求。原因是我希望每个请求执行一个相当大的内存计算,并且每个请求以线程安全的方式执行非常重要(即并发请求的计算不会混合)。
基本上,我需要一个同步队列,每次只处理一个请求,而通道看起来很适合。
然而,我无法让我的简单的 hello world 示例工作。它似乎在 'go process(w, cr)' 这一行上失败;我从服务器得到一个 200 响应,但没有内容。如果我从这一行中删除 'go',则工作正常,但我猜我没有正确调用通道。
有人能指出我错在哪里吗?
谢谢!
// 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) // 不工作;没有响应 :-(
// process(w, cr) // 工作,但是响应为空 :-(
}
func init() {
http.HandleFunc("/add", handler)
}
英文:
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.
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)
}
答案1
得分: 3
不确定这是否是正确的设计,但我怀疑问题在于当你启动第二个goroutine时,第一个goroutine继续执行并完成写入连接等操作。
为了解决这个问题,你可以使用一个waitgroup使第一个routine等待(http://golang.org/pkg/sync/#WaitGroup)。
这会破坏你将其放入线程的原因(这就是为什么我认为你有一个设计问题的原因)。
下面是一些未经测试的代码,应该可以工作,或者至少可以帮助你朝着正确的方向前进。
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)
}
希望对你有所帮助!
英文:
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)
}
答案2
得分: 1
如果大型计算不使用共享可变状态,则编写一个普通的处理程序。不需要使用通道等。
好的,大型计算确实使用共享可变状态。如果只有一个应用程序实例在运行,则使用sync.Mutex来控制对可变状态的访问。与将工作分配给单个goroutine以逐个处理计算相比,这更简单。
你是在App Engine上运行吗?你可能无法保证只有一个应用程序实例在运行。你需要使用数据存储或内存缓存来处理可变状态。如果计算可以在离线状态下完成(在请求完成后),则可以使用App Engine任务队列逐个处理计算。
顺便说一句:标题提出了解决问题的方案,但最好直接陈述问题。我本可以在上面发表评论,但我没有足够的权限。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论