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

huangapple go评论84阅读模式
英文:

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 (
	&quot;bank&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;time&quot;
)

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

	done := make(chan bool)

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

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

	fmt.Printf(&quot;Balance = %d\n&quot;, bank.Balance())
	defer log.Printf(&quot;[time] Elipsed Time: %s&quot;, 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 &lt;- amount }
func Balance() int       { return &lt;-balances }

func teller() {
	var balance int // balance is confined to teller goroutine
	for {
		select {
		case amount := &lt;-deposits:
			balance += amount
		case balances &lt;- 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 &lt; 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			bank.Deposit(1)
			done &lt;- true
		}()
	}
	go func() {
		wg.Wait()
		close(done)
	}()
	// Wait for the channel to close.
	for flag := range done {
		if !flag {
			panic(&quot;error&quot;)
		}
	}

	fmt.Printf(&quot;Balance = %d\n&quot;, bank.Balance())
	defer log.Printf(&quot;[time] Elipsed Time: %s&quot;, 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 &lt; 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			bank.Deposit(1)
		}()
	}
	wg.Wait()

	fmt.Printf(&quot;Balance = %d\n&quot;, bank.Balance())
	defer log.Printf(&quot;[time] Elipsed Time: %s&quot;, 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 &lt; 10; i++ {
		go func() {
			bank.Deposit(1)
			done &lt;- true
		}()
	}

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

	fmt.Printf(&quot;Balance = %d\n&quot;, bank.Balance())
	defer log.Printf(&quot;[time] Elipsed Time: %s&quot;, 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 &lt; 10; i++ {
    go func() {
        bank.Deposit(1)
        done &lt;- true
    }()
}

into

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

    close(done)
}() 

Note: you need to explicitly close the channel.

huangapple
  • 本文由 发表于 2022年2月6日 21:39:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/71007753.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定