Go并发,goroutine同步和关闭通道

huangapple go评论93阅读模式
英文:

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(&quot;domain&quot;, &quot;google.com&quot;, &quot;the domain u want to ping&quot;)
	flag.Parse()

	sum := 0
	ch := make(chan int)

	go start_pings(*domain, ch)

	for elapsed := range ch {
		fmt.Println(&quot;Part time: &quot; + strconv.Itoa(elapsed))
		sum += elapsed
	}

	avg := sum / 3
	fmt.Println(&quot;Average: &quot; + strconv.Itoa(avg))
}

func start_pings(domain string, ch chan int) {
	var wg sync.WaitGroup
	for i := 0; i &lt; 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 := &quot;http://&quot; + domain
	start := time.Now()

	fmt.Println(&quot;Start pinging &quot; + url + &quot;...&quot;)

	resp, err := http.Get(url)
	elapsed := time.Now().Sub(start)

	if err != nil {
		fmt.Println(err)
		return
	}
	defer resp.Body.Close()

	ch &lt;- 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 &lt; 3; i++ {
		wg.Add(1)
		go ping(domain, ch, &amp;wg)
	}
	wg.Wait()
	close(ch)
}

func ping(domain string, ch chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	url := &quot;http://&quot; + domain
	start := time.Now()

	fmt.Println(&quot;Start pinging &quot; + url + &quot;...&quot;)

	resp, err := http.Get(url)
	elapsed := time.Since(start)

	if err != nil {
		fmt.Println(err)
		return
	}
	defer resp.Body.Close()

	ch &lt;- 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 := &amp;sync.WaitGroup{} // Pointer!
	for i := 0; i &lt; 3; i++ {
		wg.Add(1)
		go ping(domain, ch, wg)
	}
	wg.Wait()
	close(ch)
}

This leaves less room for errors and misuses.

huangapple
  • 本文由 发表于 2022年8月19日 18:14:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/73415197.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定