通道奇怪的结果去goroutine

Win*_*der 0 go goroutine

当我运行goroutines时,我通常得到40作为值,我知道它的并发性,但为什么最后一个数字来了?我想输出必须是:

Page number:  34  
Page number:  12  
Page number:  8  
Page number:  2  
Page number:  29
Run Code Online (Sandbox Code Playgroud)

示例源代码:

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)
}
Run Code Online (Sandbox Code Playgroud)

pet*_*rSO 5

Go编程语言

常见问题(FAQ)

关闭作为goroutines运行的闭包会发生什么?

使用具有并发性的闭包时可能会出现一些混淆.考虑以下程序:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

人们可能错误地期望看到a,b,c作为输出.您可能会看到的是c,c,c.这是因为循环的每次迭代都使用变量v的相同实例,因此每个闭包共享该单个变量.当封闭运行,它打印在执行fmt.Println时间v的值,但V可以被修改,因为够程推出.为了帮助发现这个问题和其他问题,请运行go vet.

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

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

在此示例中,v的值作为参数传递给匿名函数.然后可以在函数内部访问该值作为变量u.

更简单的是创建一个新的变量,使用一个看似奇怪的声明样式但在Go中运行正常:

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}
Run Code Online (Sandbox Code Playgroud)

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

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

例如,

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)
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)