Skip to content

In a concurrent scenario, will the mixed use of waitgroup and goroutine cause the program to be abnormal or hang up? #47665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
daheige opened this issue Aug 12, 2021 · 3 comments

Comments

@daheige
Copy link

daheige commented Aug 12, 2021

What did you do?

type waitGroup struct {
	sync.WaitGroup
}

func (w *waitGroup) Wrap(fn func()) {
	w.Add(1)
	go func() {
		fn()
		w.Done()
	}()
}

func (w *waitGroup) WrapRecover(fn func()) {
	w.Add(1)
	go func() {
		defer func() {
			if err := recover(); err != nil {
				log.Println("exec recover: ", err)
			}
		}()

		fn()
		w.Done()
	}()
}

func getData(i int64) string {
	return strconv.FormatInt(i, 10)
}

func demo(ctx context.Context) error {
	go func() {
		ticker := time.NewTicker(500 * time.Millisecond)
		defer ticker.Stop()
		var i int64
		var wg = &waitGroup{}
		for {
			select {
			case <-ticker.C:
				data := getData(i)
				i++
				wg.Wrap(func() {
					log.Println("data: ", data)
				})
			case <-ctx.Done():
				wg.Wait()
				return
			}
		}
	}()

	return nil
}

main.go

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"syscall"
	"time"
)

var (
	port = 1339
	wait = 3 * time.Second
)

func init() {
	httpMux := http.NewServeMux()
	httpMux.HandleFunc("/debug/pprof/", pprof.Index)
	httpMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	httpMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	httpMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	httpMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
	httpMux.HandleFunc("/check", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`{"alive": true}`))
	})

	go func() {
		defer func() {
			if err := recover(); err != nil {
				log.Println("PProf exec recover: ", err)
			}
		}()

		log.Println("server PProf run on: ", port)

		if err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), httpMux); err != nil {
			log.Println("PProf listen error: ", err)
		}

	}()

}

func main() {
	ctx1, cancelFn := context.WithCancel(context.Background())
	demo(ctx1)

	// graceful exit
	ch := make(chan os.Signal, 1)
	// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
	// recv signal to exit main goroutine
	// window signal
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, syscall.SIGHUP)

	// linux signal if you use linux on production,please use this code.
	// signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2, os.Interrupt, syscall.SIGHUP)

	// Block until we receive our signal.
	sig := <-ch

	cancelFn()

	log.Println("exit signal: ", sig.String())
	// Create a deadline to wait for.
	ctx, cancel := context.WithTimeout(context.Background(), wait)
	defer cancel()

	<-ctx.Done()

	log.Println("services shutting down")
}

type waitGroup struct {
	sync.WaitGroup
}

func (w *waitGroup) Wrap(fn func()) {
	w.Add(1)
	go func() {
		fn()
		w.Done()
	}()
}

func (w *waitGroup) WrapRecover(fn func()) {
	w.Add(1)
	go func() {
		defer func() {
			if err := recover(); err != nil {
				log.Println("exec recover: ", err)
			}
		}()

		fn()
		w.Done()
	}()
}

func getData(i int64) string {
	return strconv.FormatInt(i, 10)
}

func demo(ctx context.Context) error {
	go func() {
		ticker := time.NewTicker(500 * time.Millisecond)
		defer ticker.Stop()

		var i int64
		var wg = &waitGroup{}
		for {
			select {
			case <-ticker.C:
				data := getData(i)
				i++
				wg.Wrap(func() {
					log.Println("data: ", data)
				})
			case <-ctx.Done():
				wg.Wait() // when the program exits, call the wait method here 
				return
			}
		}
	}()

	return nil
}

When I start this demo func in the web service, the ctx1 of this demo has not been cancelled. Here we use the wrap method as a callback function. The internal waitgroup is only doing Add(1), Done() operations, please pay attention to me The way it is used in this way. When I was doing pprof analysis, I found that sync block and blocking syscall are more serious indicators. Is there a problem when I use waitgroup like this?
In a concurrent scenario, will the mixed use of waitgroup and goroutine cause the program to be abnormal or hang up?

@daheige
Copy link
Author

daheige commented Aug 12, 2021

wg.Wrap(func() {
    log.Println("data: ", data)
})

What happens when I change Wrap to WrapRecover

wg.WrapRecover(func() {
    log.Println("data: ", data)
})

@seankhliao
Copy link
Member

Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.

For questions please refer to https://github.com/golang/go/wiki/Questions

@daheige
Copy link
Author

daheige commented Aug 12, 2021

Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.

For questions please refer to https://github.com/golang/go/wiki/Questions

ok

@golang golang locked and limited conversation to collaborators Aug 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants