Go 的 sync.WaitGroup 丢失了其中一个响应
Go's sync.WaitGroup lost the one of the responses
我正在尝试通过自己在 goroutine 中添加 time.Sleep
来顺序发送 http 请求。
但是,sync.WaitGroup
总是丢失一个响应,例如,下面这个go客户端向我的网络服务器发送了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 网络服务器代码
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()
wg.Wait()
只会等待所有进行 HTTP 调用的 goroutine 完成,它不会等待打印结果的 goroutine 完成。当所有 HTTP 调用都完成(并且它们的结果在通道上发送)时,wg.Wait()
可能 return,并且您的 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()
返回,我们就知道所有 HTTP 调用和传递它们的结果都已完成。我们可以在这里关闭jsonResponses
。一旦接收到在通道关闭之前发送的所有值,打印 goroutine 中的 for range
循环将正确终止。最后它将调用 wg2.Done()
,因此主程序中的 wg2.Wait()
调用可以 return 并且您的程序结束。
我正在尝试通过自己在 goroutine 中添加 time.Sleep
来顺序发送 http 请求。
但是,sync.WaitGroup
总是丢失一个响应,例如,下面这个go客户端向我的网络服务器发送了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 网络服务器代码
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()
wg.Wait()
只会等待所有进行 HTTP 调用的 goroutine 完成,它不会等待打印结果的 goroutine 完成。当所有 HTTP 调用都完成(并且它们的结果在通道上发送)时,wg.Wait()
可能 return,并且您的 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()
返回,我们就知道所有 HTTP 调用和传递它们的结果都已完成。我们可以在这里关闭jsonResponses
。一旦接收到在通道关闭之前发送的所有值,打印 goroutine 中的 for range
循环将正确终止。最后它将调用 wg2.Done()
,因此主程序中的 wg2.Wait()
调用可以 return 并且您的程序结束。