为了账号安全,请及时绑定邮箱和手机立即绑定

Goroutine 使用通道奇怪的结果

Goroutine 使用通道奇怪的结果

Go
开满天机 2021-12-07 19:33:20
当我运行 goroutines 时,我通常得到 40 作为值,我知道它的并发性,但为什么最后一个数字来了?我想输出必须是:Page number:  34  Page number:  12  Page number:  8  Page number:  2  Page number:  29示例源代码:package mainimport (    "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 = 40func 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)}
查看完整描述

2 回答

?
慕无忌1623718

TA贡献1744条经验 获得超4个赞

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


在并发使用闭包时可能会出现一些混淆。考虑以下程序:


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

    }

}

人们可能会错误地期望看到 a、b、c 作为输出。你可能会看到的是 c、c、c。这是因为循环的每次迭代都使用变量 v 的相同实例,因此每个闭包共享该单个变量。当闭包运行时,它会打印 fmt.Println 执行时 v 的值,但 v 可能在 goroutine 启动后被修改。为了帮助在这些问题和其他问题发生之前检测它们,请运行 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 // create a new '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


查看完整回答
反对 回复 2021-12-07
?
慕码人8056858

TA贡献1803条经验 获得超6个赞

我的第一个 golang 回复,可能完全关闭 :-)


循环可能如下所示:


...

for j := 0; j < MAX_TH; j++ {

    go func(x) { getWebPageContent("http://www.example.com", messages, x) }(j)

}

...

基本上,您定义一个匿名函数并使用参数调用它。你可以用不同的方式来做,但这个解决方案看起来非常实用和时尚:-)


查看完整回答
反对 回复 2021-12-07
  • 2 回答
  • 0 关注
  • 184 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信