Search code examples
godeadlockchannelgoroutine

Sending value to a channel is not ready in the select


package main

import (
    "fmt"
    "time"
)

func main() {

    ch := make(chan int)
    go func() {
        fmt.Printf("func at %d\n", time.Now().UnixNano())
        select {
        case ch <- 1:
            fmt.Println("running send")
        default:
            fmt.Println("running default")
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Printf("main at %d\n", time.Now().UnixNano())
    fmt.Println(<-ch)
}

the playground here

I have researched for a day but still cannot explain why case ch <- 1: is not ready, and the default case is selected to run. Of course, it causes deadlock!


Solution

  • From doc:

    If the channel is unbuffered, the sender blocks until the receiver has received the value. If the channel has a buffer, the sender blocks only until the value has been copied to the buffer; if the buffer is full, this means waiting until some receiver has retrieved a value.

    One way is to use this. Here's the receiver goroutine is spawned first. Also, if the receiver is not ready yet, default case will be picked up. And if it's ready, the specific case will be ready. If you run this several times, you can see either of cases happening.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ch := make(chan int)
        // goroutine acts as the reciever
        go func() {
            fmt.Printf("main at %d\n", time.Now().UnixNano())
            fmt.Println(<-ch)
        }()
        go func() {
            fmt.Printf("func at %d\n", time.Now().UnixNano())
            select {
            case ch <- 1:
                fmt.Println("running send")
            default:
                fmt.Println("running default")
            }
        }()
        time.Sleep(1 * time.Second) // Wait for the goroutines
    }
    
    

    Another solution would be to use, buffered channel:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    
        ch := make(chan int, 1)
        go func() {
            fmt.Printf("func at %d\n", time.Now().UnixNano())
            select {
            case ch <- 1:
                fmt.Println("running send")
            default:
                fmt.Println("running default")
            }
        }()
    
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("main at %d\n", time.Now().UnixNano())
        fmt.Println(<-ch)
    }
    

    Also, do read this thread on stackoverflow