0

This is my entire Go code! What confused me is that case balances <- balance: did't occurs.I dont know why?

package main

import (
    "fmt"
)


func main() {

    done := make(chan int)

    var balance int
    balances := make(chan int)
    balance = 1

    go func() {
        fmt.Println(<-balances)
        done <- 1
    }()

    select {
    case balances <- balance:
        fmt.Println("done case")

    default:
        fmt.Println("default case")
    }

    <-done

}
default case
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /tmp/sandbox575832950/prog.go:29 +0x13d

goroutine 18 [chan receive]:
main.main.func1()
    /tmp/sandbox575832950/prog.go:17 +0x38
created by main.main
    /tmp/sandbox575832950/prog.go:16 +0x97
erik258
  • 14,701
  • 2
  • 25
  • 31
benjiamin
  • 59
  • 4
  • 1
    Thank you for supplying working code that reproduces your experience. You might consider including a link to the go playground which is a nice way to facilitate responses. https://play.golang.org/p/BHl5A5cCPbA I have taken the liberty of adding the error message - include this also in the future, as it helps future readers recognize their error. – erik258 Nov 04 '21 at 05:38

3 Answers3

4

The main goroutine executes the select before the anonymous goroutine function executes the receive from balances. The main goroutine executes the default clause in the select because there is no ready receiver on balances. The main goroutine continues on to receive on done.

The goroutine blocks on receive from balances because there is no sender. Main continued past the send by taking the default clause.

The main goroutine blocks on receive from done because there is no sender. The goroutine is blocked on receive from balances.

Fix by replacing the select statement with balances <- balance. The default clause causes the problem. When the the default class is removed, all that remains in the select is send to balances.

  • Thanks for your answer,but main goroutine is sender of balances, another is receiver. I donot understand why the main goroutine didnot execute the first case? I think the `balances` channel can receive data `balance` – benjiamin Nov 04 '21 at 04:48
  • @benjiamin Oops, I swapped send/receive. Read updated answer. –  Nov 04 '21 at 04:51
3

Because of concurrency, there's no guarantee that the goroutine will execute before the select. We can see this by adding a print to the goroutine.

    go func() {
        fmt.Println("Here")
        fmt.Println(<-balances)
        done <- 1
    }()
$ go run test.go
default case
Here
fatal error: all goroutines are asleep - deadlock!
...

If the select runs first, balances <- balance would block; balances has no buffer and nothing is trying to read from it. case balances <- balance would block so select skips it and executes its default.

Then the goroutine runs and blocks reading balances. Meanwhile the main code blocks reading done. Deadlock.


You can solve this by either removing the default case from the select and allowing it to block until balances is ready to be written to.

    select {
        case balances <- balance:
            fmt.Println("done case")
    }

Or you can add a buffer to balances so it can be written to before it is read from. Then case balances <- balance does not block.

balances := make(chan int, 1)
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • Why the select run first with the unbuffered channel would blocked? – benjiamin Nov 04 '21 at 05:17
  • @benjiamin `balances <- balance` is trying to write to an unbuffered channel. No buffer means there's nowhere to store `balance`. `balances <- balance` has to block until something reads from `balances`. Instead of blocking, `select` uses its default. – Schwern Nov 04 '21 at 06:47
  • Thanks for your answer! I can understand more about unbuffered channel. – benjiamin Nov 04 '21 at 06:53
2

What confused me is that case balances <- balance: did't occurs

To be specific: it's because of select with a default case.

Whenever you create a new goroutine with go ...(), there is no guarantee about whether the invoking goroutine, or the invoked goroutine, will run next.

In practice it's likely that the next statements in the invoking goroutine will execute next (there being no particularly good reason to stop it). Of course, we should write programs that function correctly all the time, not just some, most, or even almost all the time! Concurrent programming with go ...() is all about synchronizing the goroutines so that the intended behavior must occur. Channels can do that, if used properly.

I think the balances channel can receive data

It's an unbuffered channel, so it can receive data if someone is reading from it. Otherwise, that write to the channel will block. Which brings us back to select.

Since you provided a default case, it's quite likely that the goroutine that invoked go ...() will continue to execute, and select that can't immediately choose a different case, will choose default. So it would be very unlikely for the invoked goroutine to be ready to read from balances before the main goroutine had already proceeded to try to write to it, failed, and gone on to the default case.

You can solve this by either removing the default case from the select and allowing it to block until balances is ready to be written to.

You sure can, as @Schwern points out. But it's important that you understand you don't necessarily need to use select to use channels. Instead of a select with just one case, you could instead just write

balances <- balance
fmt.Println("done")

select is not required in this case, default is working against you, and there's just one case otherwise, so there's no need for select. You want the main function to block on that channel.

you can add a buffer to balances so it can be written to before it is read from.

Sure. But again, important to understand that the fact that a channel might block both sender and receiver until they're both ready to communicate , is a valid, useful, and common use of channels. Unbuffered channels are not the cause of your problem - providing a default case for your select, and thus a path for unintended behavior, is the cause.

erik258
  • 14,701
  • 2
  • 25
  • 31
  • what you means that the unbuffered channel can receive data after someone is reading from it first . Or the channel would be blocked. – benjiamin Nov 04 '21 at 06:37
  • An unbuffered channel will block reader or writer until the other is ready. But you've given your select a way to not block, which has caused the behavior you're asking about. – erik258 Nov 04 '21 at 20:41