2013-08-02 124 views
0

我真的不知道爲什麼。Goroutines打破了方案

問題是這樣的:我和朋友一起製作了一個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 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 
} 
+0

我沒有看到這些功能有什麼問題。 Goroutines就像守護線程,如果主要的goroutine退出,所有當前的goutoutines都不會被完成。檢查以確保您的主要常規不會在此結束之前退出。 – LinearZoetrope

+0

是的,這是一個很好的事情來檢查。另外,你有沒有試過看看當你不同時運行時會發生什麼?一切正常嗎?這可能是該問題實際上並不是在goroutine中運行的事情。 – joshlf

回答

2

發生這種情況的原因是因爲調用「loadPage」的您的HandlerFunc與請求同步調用。當你在去程序中調用它時,Handler實際上會立即返回,導致響應立即發送。這就是爲什麼你得到一個空白頁面。

您可以server.go(行1096)看到這一點:

serverHandler{c.server}.ServeHTTP(w, w.req) 
if c.hijacked() { 
    return 
} 
w.finishRequest() 

ServeHTTP函數調用您的處理程序,並儘快返回它稱之爲 「finishRequest」。所以你的處理函數必須阻塞,只要它想完成請求。

使用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 
} 

如果由於某種原因,你需要做的多走程序的工作,看看sync.WaitGroup。頻道也可以工作。

如果您嘗試僅提供文件,還有其他選項已針對此進行了優化,例如FileServerServeFile

+0

這是一個很好的迴應,謝謝。好澄清..我們做這樣的事情的原因(即readfile等)是因爲我們基本上剛開始這個項目去學習golang,所以它絕對不是以最好的方式完成的。謝謝! – Arcticcu

+1

@ Arcticcu在這種情況下,我強烈建議閱讀內置軟件包的源代碼。當文檔沒有幫助時,還可以查找以「_test.go」結尾的任何文件。 – Luke

0

在圍棋典型的web框架實現,路由處理程序被調用的夠程。即在某些時候,web框架會說go loadPage(...)

所以,如果你從圍棋程序內loadPage,你有水平夠程的。

Go調度程序非常懶,如果不強制執行第二個級別,它將不會執行。所以你需要通過同步事件來執行它。例如。通過使用頻道或sync包。例如:

func loadPage(w http.ResponseWriter, path string) { 
    s := make(chan string) 
    go GetFileContent(path, s) 
    fmt.Fprint(w, <-s) 
} 

Go documentation這樣說:

如果夠程的影響必須被另一個夠程中觀察到, 使用同步機制,諸如鎖定或通道 通信以建立一個相對排序。

爲什麼這實際上是一個聰明的事情呢?在較大的項目中,您可能需要處理大量需要以某種方式有效協調的Goroutines。那麼,爲什麼叫一個Goroutine,如果它的輸出無處可用?一個有趣的事實:像fmt.Printf這樣的I/O操作也會觸發同步事件。