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?
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))
}