Search code examples
goconcurrencygoroutinego-scheduler

Ask Go to run all goroutines before continuing


I need that the Golang scheduler run all goroutines before continue, runtime.Gosched() does not solve.

The problem is that the go routine can run so fast that the "select" in start() run after the "select" inside de stopStream(), then the "case <-chanStopStream:" receiver is not ready to the sender "case retChan <- true:". The result is that when this happen the result is the same behavior from when stopStream() hangs

Run this code https://go.dev/play/p/DQ85XqjU2Q_z many times that you will see this both responses Expected response when NOT hang:

2009/11/10 23:00:00 Start
2009/11/10 23:00:00 receive chan
2009/11/10 23:00:03 end

Expected response when hang, but not when is so fast:

2009/11/10 23:00:00 Start
2009/11/10 23:00:00 default
2009/11/10 23:00:01 TIMER
2009/11/10 23:00:04 end

The code

package main

import (
    "log"
    "runtime"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {
    wg.Add(1)
    //run multiples routines on a huge system
    go start()
    wg.Wait()
}
func start() {
    log.Println("Start")
    chanStopStream := make(chan bool)
    go stopStream(chanStopStream)
    select {
    case <-chanStopStream:
        log.Println("receive chan")
    case <-time.After(time.Second): //if stopStream hangs do not wait more than 1 second
        log.Println("TIMER")
        //call some crash alert
    }
    time.Sleep(3 * time.Second)
    log.Println("end")
    wg.Done()
}

func stopStream(retChan chan bool) {
    //do some work that can be faster then caller or not
    runtime.Gosched()
    //time.Sleep(time.Microsecond) //better solution then runtime.Gosched()
    //time.Sleep(2 * time.Second) //simulate when this routine hangs more than 1 second
    select {
    case retChan <- true:
    default: //select/default is used because if the caller times out this routine will hangs forever
        log.Println("default")
    }
}


Solution

  • There is not a way to run all other goroutines before continuing execution of the current goroutine.

    Fix the problem by ensuring that the goroutine does not block on stopStream:

    Option 1: Change chanStopStream to a buffered channel. This ensures that stopStream can send a value without blocking.

    func start() {
        log.Println("Start")
        chanStopStream := make(chan bool, 1) // <--- buffered channel
        go stopStream(chanStopStream)
        ...
    }
    
    func stopStream(retChan chan bool) {
        ...
        // Always send. No select/default needed.
        retChan <- true
    }
    

    https://go.dev/play/p/xWg42TO_fIW

    Option 2: Close the channel instead of sending a value. A channel can always be closed by the sender. Receive on a closed channel returns the zero value for the channel's value type.

    func start() {
        log.Println("Start")
        chanStopStream := make(chan bool) // buffered channel not required
        go stopStream(chanStopStream)
        ...
    
    func stopStream(retChan chan bool) {
        ...
        close(retChan)
    }
    

    https://go.dev/play/p/lNfT6qzrMmO