Do channels gurantee that go routines finish?

So when we use channels in go does that guarantee that all the go routines finish or we still need to use waitgroups.
I wrote the below code which has a go routine which listens for signals.

package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
)

var pid int
var wg sync.WaitGroup

func main() {

	signl := make(chan os.Signal, 1)

	signal.Notify(signl)

	go func() {
		wg.Add(1)
		defer wg.Done()
		for {
			sig := <-signl
			if sig == syscall.SIGINT && pid != 0 {
				cmd := exec.Command("minikube", "stop")
				err := cmd.Start()
				fmt.Println("closed minikube")
				if err != nil {
					fmt.Println("failed to close minikube : ", err)
				}
				os.Exit(1)

			}
			fmt.Println("unknown signal recieveed : ", sig)

		}

	}()
	cmd1 := exec.Command("minikube", "start")

	err := cmd1.Start()
	if err != nil {
		fmt.Println("failed to start minikube : ", err)
	}
	cmd1.Wait()

	fmt.Println("starting dashboard...")
	cmd2 := exec.Command("minikube", "dashboard")
	err = cmd2.Start()
	pid = cmd2.Process.Pid
	fmt.Println("pid", pid)
	if err != nil {
		fmt.Println("failed to open dashboard : ", err)
	}
	wg.Wait()

}

in above code is wait group useless ?

Hello there. In code above your program will not exit until goroutine exits first. Since there is an endless loop inside the goroutine, it will never exit on its own. Everything depends on how and what exactly you are trying to achieve. If it’s some sort of a graceful shutdown to run some clean-ups or whatever, I use something like this:

func exitCtx(parent context.Context) (context.Context, context.CancelFunc) {
	ctx, cancel := context.WithCancel(parent)

	go func() {
		osSignalCh := make(chan os.Signal, 1)
		defer func() {
			cancel()
			signal.Stop(osSignalCh)
			close(osSignalCh)
		}()

		signal.Notify(
			osSignalCh,
			syscall.SIGINT,
			syscall.SIGTERM,
			os.Interrupt,
		)

		select {
		case <-ctx.Done():
			return
		case <-osSignalCh:
			fmt.Println()
			return
		}
	}()

	return ctx, cancel
}

In case if you just want to execute some code in goroutine and then exit your program when all of it finished, then yes, you need to sync them on your own, because it will be killed as soon as main exits. Something like this:

func DoSomething(ctx context.Context) chan error {
	done := make(chan error, 1)

	go func() {
		defer close(done)

		errBuf := errbuf.NewErrorsBuffer()

		var wg sync.WaitGroup

		wg.Add(numOfProcesses)

		for i := 0; i < numOfProcesses; i++ {
			go func() {
				defer wg.Done()

				for j := range dataChannel {
					if err := doTheThing(j); err != nil {
						errBuf.Add(err)
					}
				}
			}()
		}

		wg.Wait()

		done <- errBuf.Err()
	}()

	return done
}
1 Like

Thanks this was very helpful.