英文:
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 (
"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: ", 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 < MAX_TH; j++ {
j := j
go func() { getWebPageContent("http://www.example.com", messages, j) }()
}
For example,
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)
}
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回复,可能完全错误
循环可能看起来像这样:
...
for j := 0; j < MAX_TH; j++ {
go func(x) { getWebPageContent("http://www.example.com", messages, x) }(j)
}
...
基本上,你定义了一个匿名函数,并用一个参数调用它。你可以用不同的方式来做,但这个解决方案看起来非常简洁和高效
英文:
My first golang reply, may be completely off
The loop could probably look like this:
...
for j := 0; j < MAX_TH; j++ {
go func(x) { getWebPageContent("http://www.example.com", 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论