英文:
Go concurrency, goroutine synchronization and closing channels
问题
熟悉并发性,所以开始编写一个简单的 ping 命令行工具,使用并发调用(忽略我实际上没有测量 ping 的事实)。
问题是,我无法在等待所有 goroutine 完成的同时正确关闭通道的范围循环。
如果我想并发调用 ping 函数,我无法使用 waitgroup 进行同步,因为我会在所有 goroutine 完成之前到达 wg.Wait() 行。
有没有办法保持 ping 调用的并发性,并在它们完成后关闭通道,以便范围循环可以退出?
func main() {
domain := flag.String("domain", "google.com", "the domain u want to ping")
flag.Parse()
sum := 0
ch := make(chan int)
go start_pings(*domain, ch)
for elapsed := range ch {
fmt.Println("Part time: " + strconv.Itoa(elapsed))
sum += elapsed
}
avg := sum / 3
fmt.Println("Average: " + strconv.Itoa(avg))
}
func start_pings(domain string, ch chan int) {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go ping(domain, ch, &wg)
}
wg.Wait()
close(ch)
}
func ping(domain string, ch chan int, wg *sync.WaitGroup) {
url := "http://" + domain
start := time.Now()
fmt.Println("Start pinging " + url + "...")
resp, err := http.Get(url)
elapsed := time.Now().Sub(start)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
ch <- int(elapsed)
wg.Done()
}
英文:
Familiarizing myself with concurrency, so started writing a simple ping cli with concurrent calls (let's ignore that I'm not really measuring pings).
Problem is, I can't close the channel properly for the range loop while also waiting for all the goroutines to finish.
If I want to concurrently call the ping function, I can't synchronize it with waitgroups, because I will reach the wg.Wait() line before all the goroutines finish.
Is there a way to keep the ping calls concurrent and close the channel after they're done, so the range loop can exit?
func main() {
domain := flag.String("domain", "google.com", "the domain u want to ping")
flag.Parse()
sum := 0
ch := make(chan int)
go start_pings(*domain, ch)
for elapsed := range ch {
fmt.Println("Part time: " + strconv.Itoa(elapsed))
sum += elapsed
}
avg := sum / 3
fmt.Println("Average: " + strconv.Itoa(avg))
}
func start_pings(domain string, ch chan int) {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go ping(domain, ch, wg)
}
wg.Wait()
close(ch)
}
func ping(domain string, ch chan int, wg sync.WaitGroup) {
url := "http://" + domain
start := time.Now()
fmt.Println("Start pinging " + url + "...")
resp, err := http.Get(url)
elapsed := time.Now().Sub(start)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
ch <- int(elapsed)
wg.Done()
}
答案1
得分: 3
你不能复制一个sync.WaitGroup
!它的文档明确说明:
在第一次使用后,WaitGroup不能被复制。
传递一个指针给它:wg *sync.WaitGroup
。并且使用defer wg.Done()
来调用!你还有其他的return
语句,这会导致wg.Done()
被跳过!
func start_pings(domain string, ch chan int) {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go ping(domain, ch, &wg)
}
wg.Wait()
close(ch)
}
func ping(domain string, ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
url := "http://" + domain
start := time.Now()
fmt.Println("Start pinging " + url + "...")
resp, err := http.Get(url)
elapsed := time.Since(start)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
ch <- int(elapsed)
}
今天的IDE(和golinter)会警告这种明显的误用。为了避免这样的错误,首先将wg
声明为指针:
func start_pings(domain string, ch chan int) {
wg := &sync.WaitGroup{} // 指针!
for i := 0; i < 3; i++ {
wg.Add(1)
go ping(domain, ch, wg)
}
wg.Wait()
close(ch)
}
这样可以减少错误和误用的可能性。
英文:
You must not copy a sync.WaitGroup
! It's doc explicitly states:
> A WaitGroup must not be copied after first use.
Pass a pointer to it: wg *sync.WaitGroup
. And call wg.Done()
deferred! You have other return
statements which will cause wg.Done()
to be skipped!
func start_pings(domain string, ch chan int) {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go ping(domain, ch, &wg)
}
wg.Wait()
close(ch)
}
func ping(domain string, ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
url := "http://" + domain
start := time.Now()
fmt.Println("Start pinging " + url + "...")
resp, err := http.Get(url)
elapsed := time.Since(start)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
ch <- int(elapsed)
}
Today's IDE's (and golinter) warns about such obvious misuses. To avoid such mistakes, declare wg
to be a pointer in the first place:
func start_pings(domain string, ch chan int) {
wg := &sync.WaitGroup{} // Pointer!
for i := 0; i < 3; i++ {
wg.Add(1)
go ping(domain, ch, wg)
}
wg.Wait()
close(ch)
}
This leaves less room for errors and misuses.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论