【问题标题】:Go's sync.WaitGroup lost the one of the responsesGo 的 sync.WaitGroup 丢失了其中一个响应
【发布时间】:2018-11-20 11:59:19
【问题描述】:

我正在尝试通过自己在 goroutine 中添加 time.Sleep 来依次发送 http 请求。

但是,sync.WaitGroup 的响应总是丢失一个,例如,下面这个 go 客户端向我的 Web 服务器发送了 5 个请求,但总共只收到了 5 个响应中的 4 个:

Sending http://localhost:9001/?id=1, at 2018-06-11 17:11:56.424086867 +0800 CST m=+0.000949479
Sending http://localhost:9001/?id=2, at 2018-06-11 17:11:57.426178028 +0800 CST m=+1.003040640
GOT id: 2 sleeping .... 0.347917120258,  at: 2018-06-11 17:11:57.776187964 +0800 CST m=+1.353050576
GOT id: 1 sleeping .... 1.63133622383,  at: 2018-06-11 17:11:58.059441646 +0800 CST m=+1.636304258
Sending http://localhost:9001/?id=3, at 2018-06-11 17:11:58.42641506 +0800 CST m=+2.003277672
GOT id: 3 sleeping .... 0.959551004983,  at: 2018-06-11 17:11:59.392013618 +0800 CST m=+2.968876230
Sending http://localhost:9001/?id=4, at 2018-06-11 17:11:59.428900219 +0800 CST m=+3.005762831
GOT id: 4 sleeping .... 0.0479890727854,  at: 2018-06-11 17:11:59.479683953 +0800 CST m=+3.056546565
Sending http://localhost:9001/?id=5, at 2018-06-11 17:12:00.428293512 +0800 CST m=+4.005156124

这是 Go 客户端代码

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "sync"
    "time"
)

func main() {
    urls := []string{
      "http://localhost:9001/?id=1",
      "http://localhost:9001/?id=2",
      "http://localhost:9001/?id=3",
      "http://localhost:9001/?id=4",
      "http://localhost:9001/?id=5",
    }
    jsonResponses := make(chan string)

    var wg sync.WaitGroup

    wg.Add(len(urls))

    for i, url := range urls {
        tsleep := i
        go func(url string) {
            defer wg.Done()
            time.Sleep(time.Duration(tsleep) * time.Second)
            fmt.Println("Sending " + url + ", at " + time.Now().String())
            res, err := http.Get(url)
            if err != nil {
                log.Fatal(err)
            } else {
                defer res.Body.Close()
                body, err := ioutil.ReadAll(res.Body)
                if err != nil {
                    log.Fatal(err)
                } else {
                    t := time.Now()
                    jsonResponses <- string("GOT id: " + string(body) + ",  at: " + t.String())
                }
            }
        }(url)
    }

    go func() {
        for response := range jsonResponses {
            fmt.Println(response)
        }
    }()

    wg.Wait()
}

用我的测试tornado python web服务器代码

import tornado.ioloop
import tornado.web
import random
import tornado.gen

class DefaultHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):
        id = self.get_query_argument("id", "1")
        sleepy = 2.0 * (random.random())
        self.write(id + " sleeping .... " + str(sleepy))
        yield tornado.gen.sleep(sleepy)
        self.finish()


def make_app():
    return tornado.web.Application([
        (r"/", DefaultHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(9001)
    tornado.ioloop.IOLoop.current().start()

【问题讨论】:

    标签: go concurrency synchronization channel goroutine


    【解决方案1】:

    wg.Wait() 只会等待所有进行 HTTP 调用的 goroutine 完成,它不会等待打印结果的 goroutine 完成。当所有 HTTP 调用都完成(并且它们的结果在通道上发送)时,wg.Wait() 可能会返回,并且您的 main() 函数结束。你的应用程序也随之结束。它不会等待打印结果的独立并发 goroutine 结束。

    要让您的应用也等待,请使用第二个WaitGroup 或其他同步方式。并且不要忘记在所有 HTTP 调用完成后关闭 jsonResponses 通道,因为这将使打印 goroutine 结束(一旦在关闭之前接收到所有值):

    var wg2 sync.WaitGroup
    wg2.Add(1)
    go func() {
        defer wg2.Done()
        for response := range jsonResponses {
            fmt.Println(response)
        }
    }()
    
    wg.Wait()
    
    // At this point HTTP calls are done.
    // Close jsonResponses, signalling no more data will come:
    close(jsonResponses)
    wg2.Wait()
    

    这里发生的情况是,一旦wg.Wait() reutrns,我们就知道所有 HTTP 调用并传递它们的结果已经完成。我们可以在这里关闭jsonResponses。一旦接收到在通道关闭之前发送的所有值,打印 goroutine 中的 for range 循环将正确终止。最后它会调用wg2.Done(),所以main中的wg2.Wait()调用可以返回并且你的程序结束。

    【讨论】:

    • 谢谢!我有点新手。我已经尝试过您的解决方案——除了我忘记关闭频道,这导致整个事情挂起。感谢您指出这一点!
    猜你喜欢
    • 1970-01-01
    • 2018-08-25
    • 1970-01-01
    • 2022-11-09
    • 2023-04-01
    • 2015-01-13
    • 2014-12-02
    • 2014-01-24
    • 1970-01-01
    相关资源
    最近更新 更多