Golang中如何在SIGINT信号前延迟函数调用

Golang中如何在SIGINT信号前延迟函数调用 我的主函数大致如下:

func main() {
    dpPool := &DatabasePool{db: connectDb()}
	defer dpPool.db.Close()
	ctx := context.Background()
	dpPool.setUpTables(ctx)
	defer dpPool.dropTables(ctx)
	mux := http.NewServeMux()
	mux.HandleFunc("/", getRoot)
	mux.HandleFunc("/hello", getHello)
	mux.HandleFunc("/ping", ping)

	server := &http.Server{
		Addr:    "<address>",
		Handler: mux,
		BaseContext: func(l net.Listener) context.Context {
			ctx = context.WithValue(ctx, keyServerAddr, l.Addr().String())
			return ctx
		},
	}

	err := server.ListenAndServe()
	if errors.Is(err, http.ErrServerClosed) {
		fmt.Printf("server closed\n")
	} else if err != nil {
		fmt.Printf("error listening for server: %s\n", err)
	}
}

我正在尝试编写一个Web服务器,并希望在关闭服务器时关闭数据库连接池并删除表。我通常通过按 Ctrl + C 来关闭,这会向程序发送 SIGINT 信号。但表并没有被删除,所以我认为这与 defer 有关。任何帮助都将不胜感激。提前感谢。如果需要更多信息,请告诉我。

编辑: 我在这个 Stackoverflow 帖子 中找到了一个解决方案。这是一个有点混乱的变通方法,但它有效: 在主函数中:

    c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			fmt.Println(sig)
			dpPool.dropTables(ctx)
			fmt.Println("Dropped those tables :)")
			dpPool.db.Close()
			pprof.StopCPUProfile()
			os.Exit(1)
		}
	}()

如果有更好的方法来处理 SIGINT 中断发生时的清理函数,请告诉我。


更多关于Golang中如何在SIGINT信号前延迟函数调用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

如何捕获退出信号,就像我做的那样,以及取消上下文是什么意思

更多关于Golang中如何在SIGINT信号前延迟函数调用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好。我在主函数中创建了一个上下文,并将其传递到各处。一旦捕获到退出信号,我就取消这个上下文,并可以在原地进行清理,而不是在主函数中进行。

是的,几乎就像你做的那样。我认为不会有其他更优雅的方法了。类似这样:

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

	go func() {
		defer cancel()

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

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

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

	return ctx, cancel
}

我使用这个函数来包装上下文并将其传递给其他函数。一旦信号到来,上下文将被取消,我就能在各个地方优雅地处理关闭操作。

在Go中处理SIGINT信号时,推荐使用context和signal.NotifyContext来优雅地处理清理工作。以下是改进后的实现:

func main() {
    dpPool := &DatabasePool{db: connectDb()}
    defer dpPool.db.Close()
    
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()
    
    dpPool.setUpTables(ctx)
    defer dpPool.dropTables(ctx)
    
    mux := http.NewServeMux()
    mux.HandleFunc("/", getRoot)
    mux.HandleFunc("/hello", getHello)
    mux.HandleFunc("/ping", ping)

    server := &http.Server{
        Addr:    "<address>",
        Handler: mux,
        BaseContext: func(l net.Listener) context.Context {
            return context.WithValue(ctx, keyServerAddr, l.Addr().String())
        },
    }

    go func() {
        <-ctx.Done()
        shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        
        if err := server.Shutdown(shutdownCtx); err != nil {
            fmt.Printf("server shutdown error: %v\n", err)
        }
    }()

    err := server.ListenAndServe()
    if errors.Is(err, http.ErrServerClosed) {
        fmt.Printf("server closed\n")
    } else if err != nil {
        fmt.Printf("error listening for server: %s\n", err)
    }
}

或者使用更明确的信号处理方式:

func main() {
    dpPool := &DatabasePool{db: connectDb()}
    defer dpPool.db.Close()
    
    ctx := context.Background()
    dpPool.setUpTables(ctx)
    defer dpPool.dropTables(ctx)
    
    mux := http.NewServeMux()
    mux.HandleFunc("/", getRoot)
    mux.HandleFunc("/hello", getHello)
    mux.HandleFunc("/ping", ping)

    server := &http.Server{
        Addr:    "<address>",
        Handler: mux,
        BaseContext: func(l net.Listener) context.Context {
            return context.WithValue(ctx, keyServerAddr, l.Addr().String())
        },
    }

    done := make(chan struct{})
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    
    go func() {
        <-sigChan
        fmt.Println("Received interrupt signal, shutting down...")
        
        shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        
        if err := server.Shutdown(shutdownCtx); err != nil {
            fmt.Printf("server shutdown error: %v\n", err)
        }
        
        close(done)
    }()

    go func() {
        err := server.ListenAndServe()
        if !errors.Is(err, http.ErrServerClosed) && err != nil {
            fmt.Printf("server error: %v\n", err)
        }
    }()

    <-done
    fmt.Println("Server shutdown complete")
}

对于需要确保清理函数在SIGINT之前执行的情况,可以使用sync.Once:

func main() {
    dpPool := &DatabasePool{db: connectDb()}
    
    var cleanupOnce sync.Once
    cleanup := func() {
        cleanupOnce.Do(func() {
            dpPool.dropTables(context.Background())
            dpPool.db.Close()
        })
    }
    
    defer cleanup()
    
    ctx := context.Background()
    dpPool.setUpTables(ctx)
    
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    
    go func() {
        <-sigChan
        cleanup()
        os.Exit(0)
    }()
    
    // 服务器代码...
}
回到顶部