英文:
I applied a range to a goroutine channel, but I am getting an error. what's the problem?
问题
我正在学习goroutines和channels。我写了一段练习代码来解决goroutine并发问题。Deposit()
被调用了10次,将一个bool值传递给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
我是否遗漏了什么?问题是什么?
英文:
I am studying goroutines and channels. I wrote a practice code to figure out the concurrency problem of goroutine and solve it. Deposit()
is called 10 times, passing a bool to the done
channel. After that, this is the code that resolves the concurrency while receiving done
.
I am getting an error when I run the following code:
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
}
But I get an error.
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
am i missing something?
what's the problem?
答案1
得分: 2
死锁发生是因为运行时检测到剩余的例程被卡住,无法继续执行。
这是因为实现没有提供所需的逻辑来退出对完成通道的循环迭代。
要退出该迭代,实现必须关闭通道或跳出循环。
通常使用 WaitGroup 来解决这个问题。
> WaitGroup 用于等待一组 goroutine 完成。主 goroutine 调用 Add 来设置要等待的 goroutine 数量。然后每个 goroutine 运行并在完成时调用 Done。同时,可以使用 Wait 来阻塞,直到所有 goroutine 都完成。
>
> WaitGroup 在第一次使用后不能被复制。
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)
}()
// 等待通道关闭。
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
}()
}
// 读取相同次数的写入。
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))
}
英文:
The deadlock occurs because the runtime detected that the remaining routines were stuck and could never proceed further.
That has happen because the implementation does not provide the required logic to exit the loop iteration over the done channel.
To exit that iteration the implementation must close the channel or break out.
This is commonly solved using a 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
Though, closing the channel, in this convoluted example, is really just a burden without additional values.
This main
function could be written,
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
Though, it appears to me that removing the "concurrency" just works as good https://go.dev/play/p/qXs2oqi_1Zw
Using channels it is also possible to read at most as many times it was written.
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))
}
答案2
得分: -3
将以下代码块进行修改:
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)
}()
注意:需要显式关闭通道(channel)。
英文:
Change the following code blocks,
for i := 0; i < 10; i++ {
go func() {
bank.Deposit(1)
done <- true
}()
}
into
go func() {
for i := 0; i < 10; i++ {
bank.Deposit(1)
done <- true
}
close(done)
}()
Note: you need to explicitly close the channel.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论