我将范围应用于 goroutine 通道,但出现错误。有什么问题?

I applied a range to a goroutine channel, but I am getting an error. what's the problem?

我正在研究 goroutines 和 channels。我写了一个实践代码来搞清楚goroutine的并发问题并解决。 Deposit() 被调用 10 次,将布尔值传递给 done 通道。之后就是接收done.

时解析并发的代码

当我 运行 以下代码时出现错误:

package main

import (
    "bank"
    "fmt"
    "log"
    "time"
)

func main() {
    start := time.Now()

    done := make(chan bool)

    // Alice
    for i := 0; i < 10; i++ {
        go func() {
            bank.Deposit(1)
            done <- true
        }()
    }

    // Wait for both transactions.
    for flag := range done {
        if !flag {
            panic("error")
        }
    }

    fmt.Printf("Balance = %d\n", bank.Balance())
    defer log.Printf("[time] Elipsed Time: %s", time.Since(start))
}
package bank

var deposits = make(chan int) // send amount to deposit
var balances = make(chan int) // receive balance

func Deposit(amount int) { deposits <- amount }
func Balance() int       { return <-balances }

func teller() {
    var balance int // balance is confined to teller goroutine
    for {
        select {
        case amount := <-deposits:
            balance += amount
        case balances <- balance:
        }
    }
}

func init() {
    go teller() // start the monitor goroutine
}

但是我得到一个错误。

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /Users/kyounghwan.choi/go/main.go:48 +0xd6

goroutine 49 [select]:
bank.teller()
        /usr/local/go/src/bank/bank.go:14 +0x85
created by bank.init.0
        /usr/local/go/src/bank/bank.go:23 +0x25
exit status 2

我错过了什么吗? 有什么问题吗?

更改以下代码块,

for i := 0; i < 10; i++ {
    go func() {
        bank.Deposit(1)
        done <- true
    }()
}

进入

go func() {
    for i := 0; i < 10; i++ {
        bank.Deposit(1)
        done <- true
    }

    close(done)
}() 

注意:您需要明确关闭频道。

发生死锁是因为运行时检测到其余例程被卡住,无法继续进行。

之所以会发生这种情况,是因为实现没有提供通过 done 通道退出循环迭代所需的逻辑。

要退出该迭代,实施必须关闭通道或突破。

这通常使用 WaitGroup 来解决。

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

A WaitGroup must not be copied after first use.

func main() {
    start := time.Now()
    done := make(chan bool)

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            bank.Deposit(1)
            done <- true
        }()
    }
    go func() {
        wg.Wait()
        close(done)
    }()
    // Wait for the channel to close.
    for flag := range done {
        if !flag {
            panic("error")
        }
    }

    fmt.Printf("Balance = %d\n", bank.Balance())
    defer log.Printf("[time] Elipsed Time: %s", time.Since(start))
}

https://go.dev/play/p/pyuguc6LaEX

不过,关闭通道,在这个复杂的例子中,实际上只是一个没有附加价值的负担。

这个main函数可以写成,

func main() {
    start := time.Now()

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            bank.Deposit(1)
        }()
    }
    wg.Wait()

    fmt.Printf("Balance = %d\n", bank.Balance())
    defer log.Printf("[time] Elipsed Time: %s", time.Since(start))
}

https://go.dev/play/p/U4Zh62Rt_Be

不过,在我看来,删除“并发”同样有效https://go.dev/play/p/qXs2oqi_1Zw

使用通道也可以最多读取它写入的次数。

func main() {
    start := time.Now()

    done := make(chan bool)

    // Alice
    for i := 0; i < 10; i++ {
        go func() {
            bank.Deposit(1)
            done <- true
        }()
    }

    // Read that much writes.
    for i := 0; i < 10; i++ {
        if !<-done {
            panic("error")
        }
    }

    fmt.Printf("Balance = %d\n", bank.Balance())
    defer log.Printf("[time] Elipsed Time: %s", time.Since(start))
}