英文:
Goroutines broke the program
问题
问题是这样的:有一个web服务器。我想到在页面加载中使用goroutines会很有益,所以我继续做了以下操作:将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 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
}
英文:
The problem is this: There is a web server. I figured that it would be beneficial to use goroutines in page loading, so I went ahead and did: called loadPage function as a goroutine. However, when doing this, the server simply stops working without errors. It prints a blank, white page. The problem has to be in the function itself- something there is conflicting with the goroutine somehow.
These are the relevant functions:
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
}
答案1
得分: 2
这种情况发生的原因是因为你的HandlerFunc调用了"loadPage",它与请求同步调用。当你在一个go例程中调用它时,处理程序实际上会立即返回,导致响应立即发送。这就是为什么你得到一个空白页面的原因。
你可以在server.go(第1096行)中看到这一点:
serverHandler{c.server}.ServeHTTP(w, w.req)
if c.hijacked() {
return
}
w.finishRequest()
ServeHTTP
函数调用你的处理程序,一旦它返回,它就会调用"finishRequest"。因此,你的处理程序函数必须阻塞,只要它想要完成请求。
使用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
。通道也可以工作。
如果你只是想提供一个文件,还有其他针对此优化的选项,例如FileServer或ServeFile。
英文:
The reason why this happens is because your HandlerFunc which calls "loadPage" is called synchronously with the request. When you call it in a go routine the Handler is actually returning immediately, causing the response to be sent immediately. That's why you get a blank page.
You can see this in server.go (line 1096):
serverHandler{c.server}.ServeHTTP(w, w.req)
if c.hijacked() {
return
}
w.finishRequest()
The ServeHTTP
function calls your handler, and as soon as it returns it calls "finishRequest". So your Handler function must block as long as it wants to fulfill the request.
Using a go routine will actually not make your page any faster. Synchronizing a singe go routine with a channel, as Philip suggests, will also not help you in this case as that would be the same as not having the go routine at all.
The root of your problem is actually ioutil.ReadFile
, which buffers the entire file into memory before sending it.
If you want to stream the file you need to use os.Open
. You can use io.Copy
to stream the contents of the file to the browser, which will used chunked encoding.
That would look something like this:
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
}
If for some reason you need to do work in multiple go routines, take a look at sync.WaitGroup
. Channels can also work.
If you are trying to just serve a file, there are other options that are optimized for this, such as FileServer or ServeFile.
答案2
得分: 0
在Go中的典型Web框架实现中,路由处理程序被调用为Goroutines。也就是说,在某个时刻,Web框架会说go loadPage(...)
。
因此,如果你从loadPage
函数的内部调用一个Go协程,你就有了两个级别的Goroutines。
Go调度器非常懒惰,如果没有被强制执行,它不会执行第二级别的Goroutines。因此,你需要通过同步事件来强制执行它。例如,通过使用通道或sync
包。示例:
func loadPage(w http.ResponseWriter, path string) {
s := make(chan string)
go GetFileContent(path, s)
fmt.Fprint(w, <-s)
}
Go文档中说到:
> 如果一个Goroutine的效果必须被另一个Goroutine观察到,
> 使用同步机制,如锁或通道通信来建立相对顺序。
为什么这样做实际上是明智的呢?在较大的项目中,你可能需要处理大量需要高效协调的Goroutines。那么,如果Goroutine的输出在任何地方都没有被使用,为什么要调用它呢?有趣的是,像fmt.Printf
这样的I/O操作也会触发同步事件。
英文:
In the typical web framework implementations in Go, the route handlers are invoked as Goroutines. I.e. at some point the web framework will say go loadPage(...)
.
So if you call a Go routine from inside loadPage
, you have two levels of Goroutines.
The Go scheduler is really lazy and will not execute the second level if it's not forced to. So you need to enforce it through synchronization events. E.g. by using channels or the sync
package. Example:
func loadPage(w http.ResponseWriter, path string) {
s := make(chan string)
go GetFileContent(path, s)
fmt.Fprint(w, <-s)
}
The Go documentation says this:
> If the effects of a goroutine must be observed by another goroutine,
> use a synchronization mechanism such as a lock or channel
> communication to establish a relative ordering.
Why is this actually a smart thing to do? In larger projects you may deal with a large number of Goroutines that need to be coordinated somehow efficiently. So why call a Goroutine if it's output is used nowhere? A fun fact: I/O operations like fmt.Printf
do trigger synchronization events too.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论