CrazyInDark
CrazyInDark

Reputation: 35

does Sleep in golang block other goroutine?

Actually I'm trying to do such thing:

I have a producer and a consumer, consumer check every several minutes and count the events in the channel. But when I try to test it in go playground, I found:

package main

import (
    "fmt"
    "time"
)

func main() {

    c1 := make(chan string, 1)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {

            c1 <- "result 1"
        }
        quit <- 1
    }()

    count := 0
    for stop := false; !stop; {
        for bk := false; !bk; {
            select {
            case _, ok := <-c1:
                if ok {
                    count++
                }
            default:
                bk = true
                fmt.Println("default")
            }
        }
        select {
        case <-quit:
            fmt.Println("stop")
            stop = true
        default:
        }
        fmt.Println(count)
        time.Sleep(time.Second / 10)

    }
    fmt.Println("over")

}

No matter how long I sleep, from time.Second/10 to time.Second*10, the output will be:

default
0
default
2
default
4
default
6
default
8
default
10
default
stop
10
over

Why the goroutine can only put 2 events in the channel? I want something like:

default
0
default
stop
10
over

The problem is channel size, i just copied from other code and not checking...

Upvotes: 0

Views: 1928

Answers (3)

torek
torek

Reputation: 487735

This function:

go func() {
    for i := 0; i < 10; i++ {

        c1 <- "result 1"
    }
    quit <- 1
}()

is always—well, always when it can run at all—trying to put a string into channel c1 (until it has put in ten, anyway).

You made channel c1 with:

c1 := make(chan string, 1)

so it has room in it for one pending item. Hence if the channel is empty, the loop puts in one item, then tries to put in a second item. If at this point the channel is full—there's no guarantee that it is, but just assume it is for the moment—this goroutine now pauses, waiting for someone to yank the previous item out of the channel.

Meanwhile, at the same time plus or minus a few nanoseconds—or maybe before or after the other goroutine blocks1—you are running this other section of code. (There's no guarantee that this is the case, but it actually is the case.)

    for bk := false; !bk; {
        select {
        case _, ok := <-c1:
            if ok {
                count++
            }
        default:
            bk = true
            fmt.Println("default")
        }
    }

This code checks to see if the channel has anything in it. Because the anonymous sender got one item into it, the channel does have something in it. This code removes the one item, creating space in the channel. That causes the blocked send in the anonymous sender to run for one step now. There's no guarantee that it does so, but in fact, it actually does so—so now there's another item in the channel.

Having run that one step, though, the anonymous sender now pauses for a few nanoseconds.2 Your loop comes back to the top and checks to see if there is an item in c1. There is, so your loop takes it and counts it: you now have taken another two items. Your loop comes back to the top and checks to see if there is another item in c1. The anonymous sender is still catching his breath, or something along those lines, and has not gotten a third value into the channel—so your loop detects that the channel is empty and takes the default clause, which breaks your loop here.

The goroutine running main now prints the line default, checks to see if it should stop (no), and pauses for some fraction of a second. Some time during all of this—in practice, at the pause—the anonymous sender gets a chance to run, puts one item into the channel, and blocks at the point where it tries to put the second item into the channel. This takes only a few dozens or hundreds of nanoseconds.

The Sleep call is still blocked, as is your anonymous sender, but the wakeup will happen within some fraction of a second. When it does, your main loop goes back to the top, to run the inner !bk loop that reads items out of c1. You're now in the same state you were last time, so you will read two items out of c1 this time as well.

This repeats for the rest of the program run.

Several steps here are not guaranteed to happen this way. They just happen to actually behave this way, given the current implementation and the fact that you're running all of this on a one-CPU virtual machine. Should either of those two situations change—for instance, if you run on a multi-CPU system, or the implementation gets modified—your program's behavior might change.


1In fact, the first time through, the main routine is running and the anonymous sender has not started, hence the zero count. The main goroutine blocks in its Sleep call, which allows the anonymous sender to run on the single CPU, setting you up for the second trip through the main routine.

2Or, in this case, for as long as it takes for the main goroutine to give it a chance to run again. This specifically depends on the single-CPU aspect of the playground VM.

Upvotes: 3

p1gd0g
p1gd0g

Reputation: 711

    for stop := false; !stop; {

        for bk := false; !bk; {
            select {
            case _, ok := <-c1:
                // --------- add this ---------
                time.Sleep(time.Second / 100)
                if ok {
                    count++
                }
            default:
                bk = true
                fmt.Println("default")
            }
        }

        select {
        case <-quit:
            fmt.Println("stop")
            stop = true
        default:
        }
        fmt.Println(count)
        time.Sleep(time.Second / 10)

    }
    fmt.Println("over")

Because the channel is slower than "select default".

Upvotes: 0

Volker
Volker

Reputation: 42433

[D]oes Sleep in golang block other goroutine?

No.

Upvotes: 4

Related Questions