意外的Goroutine行为

时间:2019-01-17 10:18:48

标签: go goroutine

我是Golang的初学者

我从here.那里读到了Go中的并发性

一切正常,直到在8th slide.上向我提出问题为止
问题是:找出两个给定的二叉树是否相等。
我的方法:进行有序遍历,将两棵树中的值保存在一个切片中并进行比较。

这是我的解决方法:[不完整]

package main

import (
    "fmt"
    "golang.org/x/tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    if t != nil {
        Walk(t.Left, ch)
        ch <- t.Value
        Walk(t.Right, ch)
    }
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        fmt.Println("executing first go routing")
        Walk(t1, ch1)
        fmt.Println("closing channel [ch1]")
        close(ch1)
    }()

    go func() {
        fmt.Println("executing second go routing")
        Walk( t2, ch2 )
        fmt.Println("closing channel [ch2]")
        close(ch2)
    }()

    shouldContinue := true
    var continue1, continue2 bool
    for shouldContinue {
        select {
        case r1, ok1 := <-ch1:
            fmt.Println("[ch1] [rcvd]", r1)
            continue1 = ok1

        case r2, ok2 := <-ch2:
            fmt.Println("[ch2] [rcvd]", r2)
            continue2 = ok2
        }
        shouldContinue = continue1 || continue2
    }
    return true
}

func main() {
    Same(tree.New(1), tree.New(1))
}

我知道goroutine是协同调度的,并且如果它正在循环或连续进行计算,则一个goroutine会完全阻塞另一个。因此,我希望对于输出,它将首先从任何一个通道接收值,将其关闭,然后从另一个通道接收值,然后关闭。两者都关闭后,for循环将中断。

令我惊讶的是,先行程序从未安排好。这是我收到的输出:

executing second go routing
[ch2] [rcvd] 1
[ch2] [rcvd] 2
[ch2] [rcvd] 3
[ch2] [rcvd] 4
[ch2] [rcvd] 5
[ch2] [rcvd] 6
[ch2] [rcvd] 7
[ch2] [rcvd] 8
[ch2] [rcvd] 9
[ch2] [rcvd] 10
closing channel [ch2]
[ch2] [rcvd] 0 

有人可以解释这里发生了什么吗?一旦channel2关闭并且第二个go例程结束,为什么第一个不执行?

任何帮助将不胜感激。谢谢。

更新:
我搜寻了有关突破频道的信息,然后发现了一个问题here. 据此,我对解决方案进行了如下更新:

package main

import (
    "fmt"
    "golang.org/x/tour/tree"
    // "time"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    // time.Sleep(time.Millisecond)
    if t != nil {
        Walk(t.Left, ch)
        ch <- t.Value
        Walk(t.Right, ch)
    }
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        fmt.Println("executing first go routing")
        Walk(t1, ch1)
        fmt.Println("closing channel [ch1]")
        close(ch1)
    }()

    go func() {
        fmt.Println("executing second go routing")
        Walk( t2, ch2 )
        fmt.Println("closing channel [ch2]")
        close(ch2)
    }()

    for {
        select {
        case r1, ok1 := <-ch1:
            fmt.Println("[ch1] [rcvd]", r1)
            if !ok1 {
                ch1 = nil
            } 

        case r2, ok2 := <-ch2:
            fmt.Println("[ch2] [rcvd]", r2)
            if !ok2 {
                ch2 = nil
            }
        }
        if ch1 == nil && ch2 == nil {
            break
        }
    }
    return true
}

func main() {
    Same(tree.New(1), tree.New(1))
}

哪个给出了我认为第一个代码段会输出的确切输出:

executing second go routing
[ch2] [rcvd] 1
[ch2] [rcvd] 2
[ch2] [rcvd] 3
[ch2] [rcvd] 4
[ch2] [rcvd] 5
[ch2] [rcvd] 6
[ch2] [rcvd] 7
[ch2] [rcvd] 8
[ch2] [rcvd] 9
[ch2] [rcvd] 10
closing channel [ch2]
[ch2] [rcvd] 0
executing first go routing
[ch1] [rcvd] 1
[ch1] [rcvd] 2
[ch1] [rcvd] 3
[ch1] [rcvd] 4
[ch1] [rcvd] 5
[ch1] [rcvd] 6
[ch1] [rcvd] 7
[ch1] [rcvd] 8
[ch1] [rcvd] 9
[ch1] [rcvd] 10
closing channel [ch1]
[ch1] [rcvd] 0

我现在对发生的事情更加困惑。

2 个答案:

答案 0 :(得分:3)

  

关闭channel2后,为什么第一个不执行?

不执行通道。一遍又一遍执行的是您的选择。请注意,无论通道是否关闭,两种情况都可以始终执行。因此,可以选择id所做的第二种情况并且您中止了。 (您的中止条件看起来很糟糕:关闭两个通道(即如果两个 ok1和ok2均为假),就可以完成操作。)

不要将select本身视为“ goroutine调度工具”。它不是。它将随机选择可运行案例之一。如果所有案例的格式均为val, ok := <- ch,则所有案例都是可运行的,并且select可能始终选择第二个案例。或第一个,或...

  

[第二个解决方案]我现在对发生的事情更加困惑。

中止条件不同。当两个通道都为零时,您将中断,这将在两个通道都关闭后发生。这与您的第一个解决方案有所不同,因为第一个解决方案会在任何 一个通道关闭后中断。

这里并发性的问题不是goroutine调度,而仅仅是您进行选择的for循环的中止条件。它们不同于第一个和第二个,第一个从根本上是错误的,因为一旦任何通道用尽,它都会停止。

答案 1 :(得分:2)

在代码的第一部分,您的逻辑中有错误。

shouldContinue := true
var continue1, continue2 bool
for shouldContinue {
    select {
    case r1, ok1 := <-ch1:
        fmt.Println("[ch1] [rcvd]", r1)
        continue1 = ok1

    case r2, ok2 := <-ch2:
        fmt.Println("[ch2] [rcvd]", r2)
        continue2 = ok2
    }
    shouldContinue = continue1 || continue2
}

在上面的代码中,continue1continue2falseselect处于封锁状态,直到他的案件之一得到履行。让我们说case r2, ok2 := <-ch2:首先实现,然后continue2将是true。由于shouldContinue = continue1 || continue2这种情况,for循环将继续。由于某种原因(进行例行调度)每次case r2, ok2 := <-ch2:都满足条件。现在,ch2关闭时,ok2的值将为false,因此continue2也将为false。现在,continue1continue2都是false,因此shouldContinue也将是false。因此,它中断了for循环,您看不到ch1的输出。尝试以下方法:

continue1 = true
continue2 = true
for shouldContinue {
    select {
    case r1, ok1 := <-ch1:
        fmt.Println("[ch1] [rcvd]", r1)
        continue1 = ok1

    case r2, ok2 := <-ch2:
        fmt.Println("[ch2] [rcvd]", r2)
        continue2 = ok2
    }
    shouldContinue = continue1 || continue2
}
  

关闭通道后,无法在该通道上发送值,但仍可以从该通道接收。参见此处:https://play.golang.org/p/S4USguWfN_z

无频道始终处于阻塞状态,并且您还更改了for循环中断逻辑。这就是您的第二个解决方案起作用的原因。