Go goroutine与通道的奇怪结果

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

Go goroutine with channel strange result

问题

当我运行goroutine时,通常会得到40作为值,我知道这与并发有关,但为什么最后一个数字会出现呢?我认为输出应该是:

页面编号:34
页面编号:12
页面编号:8
页面编号:2
页面编号:29

示例源代码

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func getWebPageContent(url string, c chan int, val int) interface{} {

    if r, err := http.Get(url); err == nil {
        defer r.Body.Close()
        if body, err := ioutil.ReadAll(r.Body); err == nil {
            c <- val
            return string(body)
        }
    } else {
        fmt.Println(err)
    }
    return "XoX"

}

const MAX_TH = 40

func main() {

    // pln := fmt.Println
    messages := make(chan int)
    for j := 0; j < MAX_TH; j++ {
        go func() { getWebPageContent("http://www.example.com", messages, j) }()
    }

    routine_count := 0
    var page_number int
    for {
        page_number = <-messages
        routine_count++
        fmt.Println("页面编号:", page_number)
        if routine_count == MAX_TH {
            break
        }
    }
    close(messages)
}
英文:

When i run the goroutines, i generally get 40 as value, i know its about the concurrency but why is the last number coming? I suppose the output must be:

Page number:  34  
Page number:  12  
Page number:  8  
Page number:  2  
Page number:  29

example source code:

package main

import (
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;net/http&quot;
)

func getWebPageContent(url string, c chan int, val int) interface{} {

	if r, err := http.Get(url); err == nil {
		defer r.Body.Close()
		if body, err := ioutil.ReadAll(r.Body); err == nil {
			c &lt;- val
			return string(body)
		}
	} else {
		fmt.Println(err)
	}
	return &quot;XoX&quot;

}

const MAX_TH = 40

func main() {

	// pln := fmt.Println
	messages := make(chan int)
	for j := 0; j &lt; MAX_TH; j++ {
		go func() { getWebPageContent(&quot;http://www.example.com&quot;, messages, j) }()
	}

	routine_count := 0
	var page_number int
	for {
		page_number = &lt;-messages
		routine_count++
		fmt.Println(&quot;Page number: &quot;, page_number)
		if routine_count == MAX_TH {
			break
		}
	}
	close(messages)
}

答案1

得分: 5

Go编程语言

常见问题(FAQ)

闭包在作为goroutine运行时会发生什么?

在使用闭包与并发性时可能会引起一些困惑。考虑以下程序:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // 在退出之前等待所有的goroutine完成
    for _ = range values {
        <-done
    }
}

你可能会错误地期望看到输出为a、b、c。但实际上你可能会看到的是c、c、c。这是因为循环的每次迭代都使用相同的变量v实例,所以每个闭包都共享同一个变量。当闭包运行时,它打印的是v在执行fmt.Println时的值,但是自启动goroutine以来,v可能已经被修改过了。为了在问题发生之前检测到这种情况和其他问题,可以运行go vet。

为了将当前的v值绑定到每个闭包中,需要修改内部循环以在每次迭代时创建一个新的变量。一种方法是将变量作为参数传递给闭包:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}

在这个例子中,v的值作为参数传递给匿名函数。这个值在函数内部作为变量u可访问。

更简单的方法是只需创建一个新的变量,使用一种在Go中可能看起来奇怪但完全有效的声明方式:

for _, v := range values {
    v := v // 创建一个新的v。
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

因此,在你的情况下,通过添加语句j := j来创建一个新的变量:

for j := 0; j < MAX_TH; j++ {
    j := j
    go func() { getWebPageContent("http://www.example.com", messages, j) }()
}

例如:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func getWebPageContent(url string, c chan int, val int) interface{} {
    if r, err := http.Get(url); err == nil {
        defer r.Body.Close()
        if body, err := ioutil.ReadAll(r.Body); err == nil {
            c <- val
            return string(body)
        }
    } else {
        fmt.Println(err)
    }
    return "XoX"
}

const MAX_TH = 40

func main() {

    // pln := fmt.Println
    messages := make(chan int)
    for j := 0; j < MAX_TH; j++ {
        j := j
        go func() { getWebPageContent("http://www.example.com", messages, j) }()
    }

    routine_count := 0
    var page_number int
    for {
        page_number = <-messages
        routine_count++
        fmt.Println("Page number: ", page_number)
        if routine_count == MAX_TH {
            break
        }
    }
    close(messages)
}

输出:

Page number:  23
Page number:  6
Page number:  1
Page number:  3
Page number:  28
Page number:  32
Page number:  18
Page number:  22
Page number:  0
Page number:  36
Page number:  7
Page number:  21
Page number:  12
Page number:  2
Page number:  5
Page number:  4
Page number:  33
Page number:  13
Page number:  20
Page number:  27
Page number:  29
Page number:  8
Page number:  31
Page number:  10
Page number:  17
Page number:  25
Page number:  19
Page number:  35
Page number:  14
Page number:  38
Page number:  15
Page number:  30
Page number:  37
Page number:  39
Page number:  26
Page number:  9
Page number:  16
Page number:  11
Page number:  24
Page number:  34
英文:

> The Go Programming Language
>
> Frequently Asked Questions (FAQ)
>
> What happens with closures running as goroutines?
>
> Some confusion may arise when using closures with concurrency.
> Consider the following program:
>
> func main() {
> done := make(chan bool)
>
> values := []string{"a", "b", "c"}
> for _, v := range values {
> go func() {
> fmt.Println(v)
> done <- true
> }()
> }
>
> // wait for all goroutines to complete before exiting
> for _ = range values {
> <-done
> }
> }
>
> One might mistakenly expect to see a, b, c as the output. What you'll
> probably see instead is c, c, c. This is because each iteration of the
> loop uses the same instance of the variable v, so each closure shares
> that single variable. When the closure runs, it prints the value of v
> at the time fmt.Println is executed, but v may have been modified
> since the goroutine was launched. To help detect this and other
> problems before they happen, run go vet.
>
> To bind the current value of v to each closure as it is launched, one
> must modify the inner loop to create a new variable each iteration.
> One way is to pass the variable as an argument to the closure:
>
> for _, v := range values {
> go func(u string) {
> fmt.Println(u)
> done <- true
> }(v)
> }
>
> In this example, the value of v is passed as an argument to the
> anonymous function. That value is then accessible inside the function
> as the variable u.
>
> Even easier is just to create a new variable, using a declaration
> style that may seem odd but works fine in Go:
>
> for _, v := range values {
> v := v // create a new 'v'.
> go func() {
> fmt.Println(v)
> done <- true
> }()
> }

Therefore, in your case, create a new variable by adding the statement j := j,

for j := 0; j &lt; MAX_TH; j++ {
    j := j
    go func() { getWebPageContent(&quot;http://www.example.com&quot;, messages, j) }()
}

For example,

package main

import (
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;net/http&quot;
)

func getWebPageContent(url string, c chan int, val int) interface{} {
	if r, err := http.Get(url); err == nil {
		defer r.Body.Close()
		if body, err := ioutil.ReadAll(r.Body); err == nil {
			c &lt;- val
			return string(body)
		}
	} else {
		fmt.Println(err)
	}
	return &quot;XoX&quot;
}

const MAX_TH = 40

func main() {

	// pln := fmt.Println
	messages := make(chan int)
	for j := 0; j &lt; MAX_TH; j++ {
		j := j
		go func() { getWebPageContent(&quot;http://www.example.com&quot;, messages, j) }()
	}

	routine_count := 0
	var page_number int
	for {
		page_number = &lt;-messages
		routine_count++
		fmt.Println(&quot;Page number: &quot;, page_number)
		if routine_count == MAX_TH {
			break
		}
	}
	close(messages)
}

Output:

Page number:  23
Page number:  6
Page number:  1
Page number:  3
Page number:  28
Page number:  32
Page number:  18
Page number:  22
Page number:  0
Page number:  36
Page number:  7
Page number:  21
Page number:  12
Page number:  2
Page number:  5
Page number:  4
Page number:  33
Page number:  13
Page number:  20
Page number:  27
Page number:  29
Page number:  8
Page number:  31
Page number:  10
Page number:  17
Page number:  25
Page number:  19
Page number:  35
Page number:  14
Page number:  38
Page number:  15
Page number:  30
Page number:  37
Page number:  39
Page number:  26
Page number:  9
Page number:  16
Page number:  11
Page number:  24
Page number:  34

答案2

得分: 1

我的第一个golang回复,可能完全错误 Go goroutine与通道的奇怪结果

循环可能看起来像这样:

...
for j := 0; j < MAX_TH; j++ {
    go func(x) { getWebPageContent("http://www.example.com", messages, x) }(j)
}
...

基本上,你定义了一个匿名函数,并用一个参数调用它。你可以用不同的方式来做,但这个解决方案看起来非常简洁和高效 Go goroutine与通道的奇怪结果

英文:

My first golang reply, may be completely off Go goroutine与通道的奇怪结果

The loop could probably look like this:

...
for j := 0; j &lt; MAX_TH; j++ {
    go func(x) { getWebPageContent(&quot;http://www.example.com&quot;, messages, x) }(j)
}
...

Basically, you define an anonymous function and call it with a parameter. You could do it differently but this solution looks very functional and sleek Go goroutine与通道的奇怪结果

huangapple
  • 本文由 发表于 2016年2月8日 13:25:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/35262837.html
匿名

发表评论

匿名网友

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

确定