Hesitating on using cached / un-cached channels

85 views
Skip to first unread message

Zhaoxun Yan

unread,
Apr 11, 2022, 10:29:33 PM4/11/22
to golang-nuts
Hi guys, I have a great demonstration on why an un-cached channel might malfunction while receiving:

package main
import(
    "fmt"
    "time"
    )

func main(){
    t := time.NewTicker(time.Second * 3)
    stopnow := make(chan bool)
    //stopnow := make(chan bool, 1) //cached channel instead
    var n int

    for{
        select{
        case <-t.C:
            n++
            fmt.Printf("%d\n", n)
           
            if n==3{
                stopnow <- true //The Only Sending!!!
                t.Stop()
            }
           
        case a:=<-stopnow: //The Only Receiving!!!
            if a{
                goto END
            }
        }
    }
    END:
    fmt.Println("out of select")
}

In the code above, you will never see the printout "out of select" , because the for-select receiver is not working while the code is run inside timer receiver in the line " stopnow <- true".

So an un-cached channel like " stopnow := make(chan bool)" will occasionally but inevitably miss receiving while the code is busy processing a competing receiving.

That is why I use cached channel instead, like " stopnow := make(chan bool, 1)", which un-received message(s) will be cached until gotten received.

However there comes another vulnerability - How large should the cache be?

In a simple scenario like this, I am very confident there will be 1 message to receive only. But on a complex server, it is very common that a goroutine will receive unexpected many messages from other goroutine or functions. And it is not safe for the programmer to just guess a ceiling for the number of unprocessed messages on any time - just as to guess the maximum on how many cars can line up after a red light. If you guess wrong, only one additional message will breach the cache and cause a crash:

package main

var c = make(chan int, 1)

func main() {
   
    c <- 1
    c <- 2 //fatal error: all goroutines are asleep - deadlock!
    c <- 3

}


The code above crashes at the point that the cache of channel c is full, but the sender still puts another message into it.

What is your thought on this?
Regards,
    Zhaoxun

robert engels

unread,
Apr 12, 2022, 12:08:51 AM4/12/22
to Zhaoxun Yan, golang-nuts
There are numerous ways to create a “dead lock” in any program (this may actually be a “live lock” but I didn’t fully understand your statement - this is just one of them. 


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/fefc350d-3423-4fb6-b844-703ce03c7e5fn%40googlegroups.com.

burak serdar

unread,
Apr 12, 2022, 12:31:55 AM4/12/22
to robert engels, Zhaoxun Yan, golang-nuts
An unbuffered channel is a synchronization mechanism. A send on an unbuffered channel will block until there is a concurrent receive from another goroutine.

Your program has only one goroutine. A send to an unbuffered channel will always deadlock, so will a receive from it. So the problem you are describing is not a problem related to the way unbuffered channels work, but it is due to the way the program is organized: it is written to deadlock. With a buffered channel, it can run. This program does not need a buffer more than 1. Once the send runs, the channel will be full, and the second case will be activated. The first case cannot run until the second case is done, which will terminate the loop.

Zhaoxun Yan

unread,
Apr 12, 2022, 9:50:02 AM4/12/22
to burak serdar, robert engels, golang-nuts
Yes you are right! Unbuffered channel is solid!

package main

import (
    "fmt"
    "time"
    )

var c = make(chan int)

func report(){
    for{
        select{
        case n:= <- c:
            fmt.Println(n, time.Now().Format("2006-01-02 15:04:05"))
        default:
            time.Sleep(time.Second)
        }
    }
}

func main() {
   
    go report()
   
    for i:=1; i<=5; i++{
        c <- i
    }

}


Reply all
Reply to author
Forward
0 new messages