Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Signal goroutines to stop with channel close

Tags:

go

channel

I have got multiple goroutines that select from two channels: one chan provides data, one chan for signals (kind of done/quit channel).

I use the signals channel to capture signals (kill) and gracefully close the goroutines.

I'm running the 'worker' goroutines from package a, while the goroutine func that captures signals runs from package b.

I use the signals package from https://gist.github.com/reiki4040/be3705f307d3cd136e85.

package a

import "sync"

WorkChan := make(chan int)
QuitChan := make(chan struct{})

func Stop() {
        fmt.Println("Stop called, closing channel")
        close(QuitChan)
}

func Work(wg *sync.WaitGroup) {
    var item int
    for {
        select {
        case item = <- WorkChan:
            ... processing
        case <- QuitChan:
            wg.Done()
            return
        }
    }
}

The goroutine to catch signals, and call a.Stop()

package b

import (
    "os/signal"
    "os"
    "syscal"
    "a"
)

func Signal() {

    sChan := make(chan os.Signal, 1)
    signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT)

    for {
        s := <-sChan
        switch s {
        case os.Interrupt, syscall.SIGTERM:
            a.Stop()
        }
    }
}

and this is my main func

package main

import (
    "a"
    "b"
    "sync"
)

func main() {

    var wg sync.WaitGroup

    go b.Signal()

    wg.Add(1) // for simplicity; actual code start multiple goroutines of Work
    go a.Work(&wg)

    // wait until work is done
    wg.Wait()
    fmt.Println("Done.")
}

When I kill the running process, I see the printed message from Quit. I expected that once the channel is closed, the goroutines will select the QuitChan case at some point and return.

But they keep running; they continue to process items from WorkChan. seems like it is ignored. What am I missing here? Doesn't the channel get closed? How come it is still open?

like image 531
Chen A. Avatar asked Nov 12 '18 21:11

Chen A.


1 Answers

First I think you should make a simple test, and past it out. It will be more help to let other understand what's your problem.

I changed your code, and make it reading like a go code, instead of other language. Now it's worked.

In your code, there are some mistakes, I marked it as ERROR comment. Some are grammar error, like creating WorkChan. Some are type error.

One import design thing you should know, when you want exit after execute Stop(), you should close the WorkChan where you send data to WorkChan, insteal of just return at where you receive date.

  • a.go

    package a
    
    import (
        "fmt"
        "sync"
    )
    
    // ERROR: can not do make in global
    var WorkChan chan int
    var QuitChan chan struct{}
    
    // Create chan when init
    func init() {
        fmt.Println("Init a")
        WorkChan = make(chan int)
        QuitChan = make(chan struct{})
    }
    
    func Stop() {
        fmt.Println("Stop called, closing quit channel")
        close(QuitChan)
    }
    
    // Close the work channel where you send date
    func Start(wg *sync.WaitGroup) {
        i := 0
        for {
            select {
            case <-QuitChan:
                fmt.Println("Closing work chan")
                close(WorkChan)
                wg.Done()
                return
            default:
                WorkChan <- i
                i++
            }
        }
    }
    
    // Work will exit when workchan closed
    func Work(wg *sync.WaitGroup) {
        for item := range WorkChan {
            fmt.Printf("Receive %d\n", item)
        }
        wg.Done()
        fmt.Println("Work exit")
    }
    
  • b.go

    package b
    
    import (
        "github.com/shitaibin/awesome/a"
        "os"
        "os/signal"
        "syscall"
    )
    
    func Signal() {
    
        sChan := make(chan os.Signal, 1)
        signal.Notify(sChan, syscall.SIGTERM, syscall.SIGINT) // ERROR
    
        for {
            s := <-sChan
            switch s {
            case os.Interrupt, syscall.SIGTERM:
                a.Stop()
                return // should return free resource
            }
        }
    }
    
  • main.go

    package main
    
    import (
        "fmt"
        "github.com/shitaibin/awesome/a"
        "github.com/shitaibin/awesome/b"
        "sync"
    )
    
    func main() {
    
        var wg sync.WaitGroup
    
        go b.Signal()
    
        wg.Add(1)      // for simplicity; actual code start multiple goroutines of Work
        go a.Work(&wg) // ERROR: pointer
    
        wg.Add(1)
        go a.Start(&wg) // Send data and close channel when stop
    
        // wait until work is done
        wg.Wait()
        fmt.Println("Done.")
    }
    
  • Result

    // omit
    Receive 87028
    Receive 87029
    Receive 87030
    Receive 87031
    Receive 87032
    Receiv^C101    <---- send signal here
    Receive 87102
    Receive 87103
    Receive 87104
    Receive 87105
    Receive 87106
    Receive 87107
    Receive 87108
    Receive 87109
    Receive 87110
    Stop called, closing quit channel
    Receive 87111
    Receive 87112
    Closing work chan
    Work exit
    Done.
    
like image 125
James Shi Avatar answered Oct 02 '22 10:10

James Shi