8

Go を少しいじっていますが、解決できない問題があります。

次のコードは、私の問題を再現する可能性が最も低いコードです。元のコードの目的は、http リクエストをゴルーチンに委譲することです。各ゴルーチンは少し重い画像計算を行い、応答することになっています。

package main

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

func main() {
    http.HandleFunc("/", handle)
    http.ListenAndServe(":8080", nil)
}

func handle(w http.ResponseWriter, r *http.Request) {

    // the idea is to be able to handle several requests
    // in parallel

    // the "go" is problematic
    go delegate(w)
}

func delegate(w http.ResponseWriter) {

    // do some heavy calculations first

    // present the result (in the original code, the image)
    fmt.Fprint(w, "hello")
}

の場合はgo delegate(w)応答がありませんが、 がないgoとうまくいきます。

誰が何が起こっているのか説明できますか? どうもありがとう!

4

3 に答える 3

5

ListenAndServeはすでにゴルーチンを起動してハンドラー関数を呼び出しているため、自分で行うべきではありません。

パッケージソースからの関連する関数のコードは次のとおりです。

1089    func ListenAndServe(addr string, handler Handler) error {
1090        server := &Server{Addr: addr, Handler: handler}
1091        return server.ListenAndServe()
1092    }


1010    func (srv *Server) ListenAndServe() error {
1011        addr := srv.Addr
1012        if addr == "" {
1013            addr = ":http"
1014        }
1015        l, e := net.Listen("tcp", addr)
1016        if e != nil {
1017            return e
1018        }
1019        return srv.Serve(l)
1020    }


1025    func (srv *Server) Serve(l net.Listener) error {
1026        defer l.Close()
1027        var tempDelay time.Duration // how long to sleep on accept failure
1028        for {

1057            go c.serve()
1058        }
1059        panic("not reached")
1060    }


579 // Serve a new connection.
580 func (c *conn) serve() {
581     defer func() {
582         err := recover()

669         handler.ServeHTTP(w, w.req)

したがって、コードは単純に

func handle(w http.ResponseWriter, r *http.Request) {
    // the idea is to be able to handle several requests
    // in parallel
    // do some heavy calculations first

    // present the result (in the original code, the image)
    fmt.Fprint(w, "hello")
}
于 2012-10-22T20:10:28.050 に答える
1

Goスケジューラーは、Goroutinesにとって本当に無慈悲な場合があります。問題はこれです:あなたはアプリケーションを持っていて、goルーチンを実行しているので、スケジューラーは考えます:ねえ、私は実際にいくつかの最適化を行うかもしれません。CPU時間を節約し、アプリケーションの応答性を高めるために、後でこの特定のGoroutineを実行しないのはなぜですか?

これが起こることです:あなたのコードでは、Goroutineをある時点で終了させる方法はありません。実際、Goのドキュメントには次のように書かれています。

たとえば、このプログラムでは次のようになります。

 var a string

 func hello() {
   go func() { a = "hello" }()
   print(a)
 }

aへの割り当ての後に同期イベントが続くことはないため、他のゴルーチンによって監視されることが保証されません。実際、アグレッシブなコンパイラーはgoステートメント全体を削除する可能性があります。

ゴルーチンの効果を別のゴルーチンで観察する必要がある場合は、ロックやチャネル通信などの同期メカニズムを使用して、相対的な順序を確立します。

したがって、問題の解決策は、たとえばチャネルを使用して、同期イベントを追加することです。

パッケージメイン

import (
  "fmt"
  "net/http"
)

func main() {
    http.HandleFunc("/", handle)
    http.ListenAndServe(":8080", nil)
}

func handle(w http.ResponseWriter, r *http.Request) {
    // the idea is to be able to handle several requests
    // in parallel

    // the "go" is problematic...
    ch := make(chan int)
    go delegate(w, ch)
    // ...but not anymore:
    <-ch
}

func delegate(w http.ResponseWriter, ch chan<- int) {
    // do some heavy calculations first
    // present the result (in the original code, the image)
    fmt.Fprint(w, "hello")
    ch <- 1
}

From:Goメモリモデル

とにかく、他の人が指摘しているように、あなたの例は現在一種の人工的なものです。しかし、httpハンドラー内から他のGoroutineを呼び出すことが理にかなっているシナリオは確かにあります。たとえば、重い計算とHTTPストリーミングを同時に行う場合、または複数の重い計算を同時に行う場合です。後者の場合、同期のために自分でチャネルを追加したと思いますが。

于 2012-10-22T21:28:52.927 に答える
1

ハンドラーは、「外側の」ゴルーチン (リクエストごとのゴルーチン) から既に呼び出されています。ハンドラーは、完全な応答を書き込むなど、実行する必要があることをすべて実行してから戻る必要があります。余分な go ステートメントの b/c を「時期尚早に」返しています。「デリゲート」の本体を「ハンドル」に入れて、それが何かを改善するかどうかを確認してください;-)

于 2012-10-22T20:11:06.457 に答える