4

INTRODUCTION:

I am just starting to learn the Go language and have reached the lesson about concurrency.

I have invented a small task for myself to try to implement what I have learned about closing goroutines.

PROBLEM:

If we close channel it's case will always be selected in the select statement, which is a great way to broadcast cancelation signal to all goroutines.

Below I have 2 goroutines and a quit channel that never gets received.

Code timeouts on Playground.

If I comment out default part in the goroutines then quit signal gets received.

QUESTION:

I really do not understand why is this happening and how to fix it, although I am trying.
Can somebody please explain me what is the problem and offer some advice on how to solve it?

package main

import (
    "fmt"
    "sync"
    "time"
)

func positive_numbers(quit chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()
    i := 1
    for {
        select {
        case <-quit:
            fmt.Println("[+]Quiting...")
            return
        default:
            fmt.Printf("%v ", i)
            i++
        }
    }
}

func negative_numbers(quit chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()
    i := -1
    for {
        select {
        case <-quit:
            fmt.Println("[-]Quiting...")
            return
        default:
            fmt.Printf("%v ", i)
            i--
        }
    }
}

func main() {
    quit := make(chan struct{})

    wg := sync.WaitGroup{} // so we can wait for all goroutines to finish
    wg.Add(2)

    go positive_numbers(quit, &wg)
    go negative_numbers(quit, &wg)

    go func(quit chan struct{}) {
        defer close(quit)
        time.Sleep(1 * time.Second)
    }(quit)

    wg.Wait()
}
jub0bs
  • 60,866
  • 25
  • 183
  • 186
AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • 1
    Does this answer your question? [Goroutines are cooperatively scheduled. Does that mean that goroutines that don't yield execution will cause goroutines to run one by one?](https://stackoverflow.com/questions/37469995/goroutines-are-cooperatively-scheduled-does-that-mean-that-goroutines-that-don) – zerkms Aug 13 '21 at 07:21
  • small detail https://golang.org/doc/faq#go_or_golang –  Aug 13 '21 at 07:30
  • @mh-cbon that quote confirms what OP said: "If we close channel it's case will always be selected in the select statement" – zerkms Aug 13 '21 at 07:33
  • @zerkms my bad. –  Aug 13 '21 at 07:37
  • @hobbs so the question boils down to, my code does not work on the playground ? –  Aug 13 '21 at 07:42
  • The `default` case makes your goroutines non-blocking. Hence when you remove `default`, goroutine waits for quit to receive something. I think with `default` it sucks all the CPU available on the go playground and you are getting timeout. I added some delay in your code in the `default` case and it's not timing out now. https://play.golang.org/p/poxcGhwoGBK – Nick Aug 13 '21 at 08:34

1 Answers1

6

This code works alright in real life; it's just not compatible with the playground's "fake time" because positive_numbers and negative_numbers don't block, and the runtime can't decide how many iterations of each get to run before the Sleep expires (basically, the prints take zero emulated wallclock time, so they try to produce infinite output, and you hit the playground CPU usage limit without ever advancing the wallclock time at all). On real machines, where you can only print finite output in finite time, you get reasonable behavior.

In the playground, if you add something like time.Sleep(time.Millisecond) after each print, you force the fake clock to move forward, and the program also terminates.

hobbs
  • 223,387
  • 19
  • 210
  • 288