最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang中Context.WithCancel 的實(shí)戰(zhàn)指南

 更新時(shí)間:2025年11月02日 11:47:19   作者:言之。  
本文介紹了Go語(yǔ)言中context包的核心用法,重點(diǎn)講解了context.Background()和context.WithCancel()的使用場(chǎng)景,具有一定的參考價(jià)值,感興趣的可以了解一下

1. 它到底做了什么

  • context.Background():創(chuàng)建一個(gè)根上下文(root context)。它永不取消、不超時(shí)、不攜帶值,適合作為整個(gè)程序的起點(diǎn)(main、初始化、測(cè)試)。
  • context.WithCancel(parent):基于父上下文 parent 派生一個(gè)可取消的子上下文 ctx,并返回一個(gè)取消函數(shù) cancel。調(diào)用 cancel() 或父上下文被取消時(shí),ctx.Done() 會(huì)被關(guān)閉,ctx.Err() 返回 context.Canceled。

關(guān)鍵點(diǎn):取消是向下傳播的。取消父 ctx,會(huì)取消它的所有子孫;取消子 ctx,不會(huì)影響父親或兄弟。

2. 何時(shí)應(yīng)當(dāng)用WithCancel(context.Background())

  • main() 頂層管理應(yīng)用全局生命周期,如優(yōu)雅退出、統(tǒng)一扇出/扇入的 goroutine 管理。
  • 在沒(méi)有現(xiàn)成“上游 ctx”的程序入口(腳本、守護(hù)進(jìn)程、批處理)里,作為創(chuàng)建樹(shù)狀任務(wù)。
  • 但在 HTTP/RPC 等請(qǐng)求范圍內(nèi),不要憑空造根;應(yīng)使用 req.Context() 繼續(xù)傳遞。

如果是捕獲系統(tǒng)信號(hào)(Ctrl+C、SIGTERM)觸發(fā)取消,優(yōu)先用 signal.NotifyContext(Go 1.20+),比“Background + WithCancel + 自己收信號(hào)”更簡(jiǎn)潔。

3. 基本用法示例

package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context, id int) error {
	ticker := time.NewTicker(200 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			// 必須尊重取消
			return ctx.Err()
		case <-ticker.C:
			fmt.Println("doing work", id)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 確保資源釋放,哪怕下面提前 return

	go func() {
		if err := worker(ctx, 1); err != nil {
			fmt.Println("worker exit:", err)
		}
	}()

	time.Sleep(1 * time.Second)
	cancel() // 觸發(fā)所有使用 ctx 的協(xié)程退出
	time.Sleep(200 * time.Millisecond)
}

要點(diǎn):

  • 永遠(yuǎn)在合適的位置 defer cancel(),避免泄漏。
  • worker 必須在循環(huán)里 select <-ctx.Done(),才能及時(shí)退出。

4. 扇出/扇入與錯(cuò)誤快速失敗

在并發(fā)扇出場(chǎng)景,拿到第一個(gè)錯(cuò)誤就取消其余任務(wù):

func fetchAll(ctx context.Context, urls []string) error {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	errCh := make(chan error, len(urls))
	var wg sync.WaitGroup

	for _, u := range urls {
		wg.Add(1)
		go func(u string) {
			defer wg.Done()
			// 你的 I/O 操作必須支持 ctx(HTTP 請(qǐng)求要傳 ctx)
			if err := fetchOne(ctx, u); err != nil {
				errCh <- err
				cancel() // 快速失敗,通知其他 goroutine 停止
			}
		}(u)
	}

	wg.Wait()
	close(errCh)
	for err := range errCh {
		if err != nil {
			return err
		}
	}
	return nil
}

5. 與WithTimeout/WithDeadline的選擇

  • WithCancel:只手動(dòng)取消,不設(shè)超時(shí)。適合“由業(yè)務(wù)條件/信號(hào)決定停止”的情況。
  • WithTimeout:到時(shí)間自動(dòng)取消,ctx.Err() == context.DeadlineExceeded。
  • WithDeadline:指定絕對(duì)時(shí)間點(diǎn)取消。

實(shí)踐建議:

  • 如果有時(shí)間邊界,就用 WithTimeout/WithDeadline。
  • 只有在明確需要手動(dòng)控制時(shí),才用純 WithCancel。

6. 常見(jiàn)坑與反模式

  1. 忘記調(diào)用 cancel()
    即便父 ctx 會(huì)被取消,你也應(yīng)該調(diào)用返回的 cancel() 來(lái)釋放內(nèi)部計(jì)時(shí)器/子關(guān)系,避免泄漏。
  2. 庫(kù)函數(shù)內(nèi)部創(chuàng)建根 ctx
    庫(kù)函數(shù)不應(yīng) context.Background() 作為根;應(yīng)當(dāng)接收調(diào)用方傳入的 ctx。只有在 main、測(cè)試或初始化才創(chuàng)建根。
  3. 協(xié)程不檢查 ctx.Done()
    導(dǎo)致任務(wù)無(wú)法停止,程序卡住或泄漏 goroutine。
  4. 把 context 存到結(jié)構(gòu)體字段長(zhǎng)期持有
    context 應(yīng)該顯式參數(shù)傳遞到需要的調(diào)用鏈,避免生命周期混亂。
  5. 拿 context.Value 當(dāng)參數(shù)包
    Value 只用于跨 API 邊界的請(qǐng)求范圍元數(shù)據(jù)(trace id、auth token),不要當(dāng)通用參數(shù)傳遞器。

7. 取消語(yǔ)義與錯(cuò)誤判斷

  • cancel()多次調(diào)用,冪等。

  • 一旦取消,<-ctx.Done() 立即可讀;ctx.Err() 為:

    • context.Canceled:手動(dòng)取消或上游取消。
    • context.DeadlineExceeded:超時(shí)/到期。
  • 下游函數(shù)應(yīng)盡量返回 ctx.Err(),方便上游統(tǒng)一識(shí)別是業(yè)務(wù)錯(cuò)誤還是取消/超時(shí)。

8. 與外部 I/O 的協(xié)作

要讓取消生效,外部操作必須接收并使用 ctx。例如:

  • http.NewRequestWithContext(ctx, ...)
  • 數(shù)據(jù)庫(kù)驅(qū)動(dòng)的 QueryContext/ExecContext
  • gRPC 的 client.Do(ctx, ...)

如果第三方庫(kù)不支持 ctx,考慮:

  • 封裝在可中斷的 goroutine 內(nèi),配合通道/關(guān)閉;或
  • 在外層加 WithTimeout,并確保 I/O 可以被系統(tǒng)打斷(例如設(shè)置 socket deadline)。

9. 實(shí)戰(zhàn)模式:優(yōu)雅退出(信號(hào)觸發(fā))

func main() {
    // 更推薦:signal.NotifyContext
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    g, gctx := errgroup.WithContext(ctx)

    g.Go(func() error { return runHTTPServer(gctx) })
    g.Go(func() error { return runWorkers(gctx) })

    if err := g.Wait(); err != nil && !errors.Is(err, context.Canceled) {
        log.Fatal(err)
    }
}

說(shuō)明:

  • signal.NotifyContext 內(nèi)部相當(dāng)于 WithCancel(Background()) + 收信號(hào)后 cancel()。
  • errgroup.WithContext 能在第一個(gè) goroutine 出錯(cuò)后自動(dòng)取消其余 goroutine。

10. 簡(jiǎn)明清單

  • 在 main/初始化:ctx := context.Background() → 需要手動(dòng)控制時(shí) ctx, cancel := context.WithCancel(ctx),并 defer cancel()。
  • 傳遞 ctx 到所有 I/O/API,循環(huán)內(nèi) select 監(jiān)聽(tīng) ctx.Done()。
  • 有時(shí)間邊界就用 WithTimeout/WithDeadline。
  • 庫(kù)函數(shù)不要?jiǎng)?chuàng)建根 ctx;不要把 ctx 存結(jié)構(gòu)體;不要濫用 Value。
  • 錯(cuò)誤處理要區(qū)分業(yè)務(wù)錯(cuò)誤與 context.Canceled / DeadlineExceeded。

一個(gè)典型的生產(chǎn)場(chǎng)景:優(yōu)雅關(guān)停 HTTP 服務(wù),

確保在收到 SIGTERM/Ctrl+C 后,不再接受新請(qǐng)求,并等待正在處理的請(qǐng)求完成。

1. 背景

HTTP 服務(wù)的 http.Server 從 Go 1.8 起支持 Shutdown(ctx) 方法,它會(huì):

  1. 停止監(jiān)聽(tīng)新連接。
  2. 等待已有連接上的請(qǐng)求完成(直到超時(shí)或 ctx 取消)。

我們就可以用 context.WithCancel + 信號(hào)監(jiān)聽(tīng) 來(lái)觸發(fā)這個(gè)流程。

2. 示例代碼

package main

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

func main() {
	// 1. 創(chuàng)建根 ctx,并能在收到信號(hào)時(shí)取消
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop() // 釋放資源

	// 2. 創(chuàng)建 HTTP server
	mux := http.NewServeMux()
	mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
		// 模擬一個(gè)慢請(qǐng)求,且支持 ctx 取消
		select {
		case <-time.After(5 * time.Second):
			fmt.Fprintln(w, "done")
		case <-r.Context().Done():
			// 客戶端斷開(kāi)或服務(wù)關(guān)停時(shí)走這里
			log.Println("request canceled:", r.Context().Err())
		}
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	// 3. 啟動(dòng)服務(wù)
	go func() {
		log.Println("HTTP server started on :8080")
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("ListenAndServe error: %v", err)
		}
	}()

	// 4. 阻塞等待信號(hào)
	<-ctx.Done()
	log.Println("Shutdown signal received")

	// 5. 創(chuàng)建超時(shí) ctx 來(lái)優(yōu)雅關(guān)停
	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		log.Fatalf("HTTP server Shutdown error: %v", err)
	}
	log.Println("HTTP server exited gracefully")
}

3. 運(yùn)行流程

  1. 啟動(dòng)程序后,srv.ListenAndServe() 在獨(dú)立 goroutine 監(jiān)聽(tīng)請(qǐng)求。

  2. 主 goroutine 通過(guò) <-ctx.Done() 等待信號(hào)觸發(fā)。

  3. 收到 SIGTERM/Ctrl+C 時(shí):

    • signal.NotifyContext 內(nèi)部調(diào)用 cancel() → 主 goroutine 繼續(xù)執(zhí)行。
    • 調(diào)用 srv.Shutdown(shutdownCtx),阻止新連接,等待已有請(qǐng)求完成。
  4. 如果 10 秒超時(shí)未完成,Shutdown 會(huì)強(qiáng)制關(guān)閉連接。

4. 關(guān)鍵點(diǎn)說(shuō)明

  • 為什么用 signal.NotifyContext 而不是 WithCancel(context.Background()) 手動(dòng)監(jiān)聽(tīng)信號(hào)?

    • signal.NotifyContext 是 Go 1.20+ 官方推薦方式,內(nèi)部封裝了 WithCancel,更簡(jiǎn)潔,不會(huì)忘記 defer stop()。
  • 為什么 Shutdown 用新的 context.Background() 而不是主 ctx?

    • 主 ctx 已經(jīng)被取消,必須新建一個(gè)超時(shí) ctx,才能控制關(guān)停時(shí)的等待時(shí)間。
  • 為什么 handler 里用 r.Context()?

    • 每個(gè) HTTP 請(qǐng)求都帶有獨(dú)立的 Context,在客戶端斷開(kāi)、服務(wù)器關(guān)停時(shí)會(huì)自動(dòng)取消,可以及時(shí)釋放資源。

5. 常見(jiàn)擴(kuò)展模式

  1. 多服務(wù)關(guān)停(HTTP + Kafka + gRPC 等)
    把 ctx 傳給所有子服務(wù),每個(gè)子服務(wù)在 ctx.Done() 時(shí)執(zhí)行自己的關(guān)停邏輯。
  2. 健康檢查 / readiness
    在關(guān)停流程里,先修改健康檢查狀態(tài)(例如 /healthz 返回非 200),再執(zhí)行 Shutdown。
  3. 并發(fā)任務(wù)收尾
    用 errgroup.WithContext(ctx) 管理后臺(tái)任務(wù),信號(hào)到達(dá)時(shí)全部取消。

到此這篇關(guān)于Golang中Context.WithCancel 的實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Golang Context.WithCancel 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang開(kāi)發(fā)動(dòng)態(tài)庫(kù)的實(shí)現(xiàn)

    Golang開(kāi)發(fā)動(dòng)態(tài)庫(kù)的實(shí)現(xiàn)

    這篇文章主要介紹了Golang開(kāi)發(fā)動(dòng)態(tài)庫(kù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • goland設(shè)置控制臺(tái)折疊效果

    goland設(shè)置控制臺(tái)折疊效果

    這篇文章主要介紹了goland設(shè)置控制臺(tái)折疊效果,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • golang?Gorm框架講解

    golang?Gorm框架講解

    gorm是一個(gè)使用Go語(yǔ)言編寫的ORM框架,這篇文章主要介紹了golang?Gorm框架,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-12-12
  • 使用Golang?Validator包實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證詳解

    使用Golang?Validator包實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證詳解

    在開(kāi)發(fā)過(guò)程中,數(shù)據(jù)驗(yàn)證是一個(gè)非常重要的環(huán)節(jié),而golang中的Validator包是一個(gè)非常常用和強(qiáng)大的數(shù)據(jù)驗(yàn)證工具,提供了簡(jiǎn)單易用的API和豐富的驗(yàn)證規(guī)則,下面我們就來(lái)看看Validator包的具體使用吧
    2023-12-12
  • Go 微服務(wù)開(kāi)發(fā)框架DMicro設(shè)計(jì)思路詳解

    Go 微服務(wù)開(kāi)發(fā)框架DMicro設(shè)計(jì)思路詳解

    這篇文章主要為大家介紹了Go 微服務(wù)開(kāi)發(fā)框架DMicro設(shè)計(jì)思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Go語(yǔ)言集成開(kāi)發(fā)環(huán)境之VS Code安裝使用

    Go語(yǔ)言集成開(kāi)發(fā)環(huán)境之VS Code安裝使用

    VS Code是微軟開(kāi)源的一款編輯器,插件系統(tǒng)十分的豐富,下面介紹如何用VS Code搭建go語(yǔ)言開(kāi)發(fā)環(huán)境,需要的朋友可以參考下
    2021-10-10
  • Go 切片導(dǎo)致內(nèi)存泄露的幾種原因

    Go 切片導(dǎo)致內(nèi)存泄露的幾種原因

    某些情況下,對(duì)一個(gè)已存在的切片或數(shù)組進(jìn)行切分操作可能會(huì)導(dǎo)致內(nèi)存泄漏,本文主要介紹了Go 切片導(dǎo)致內(nèi)存泄露的幾種原因,感興趣的可以了解一下
    2023-05-05
  • Go語(yǔ)言中三個(gè)輸入函數(shù)(scanf,scan,scanln)的區(qū)別解析

    Go語(yǔ)言中三個(gè)輸入函數(shù)(scanf,scan,scanln)的區(qū)別解析

    本文詳細(xì)介紹了Go語(yǔ)言中三個(gè)輸入函數(shù)Scanf、Scan和Scanln的區(qū)別,包括用法、功能和輸入終止條件等,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • 用golang如何替換某個(gè)文件中的字符串

    用golang如何替換某個(gè)文件中的字符串

    這篇文章主要介紹了用golang實(shí)現(xiàn)替換某個(gè)文件中的字符串操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • 詳解Go語(yǔ)言中for range的

    詳解Go語(yǔ)言中for range的"坑"

    這篇文章主要介紹了詳解Go語(yǔ)言中for range的"坑",文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評(píng)論

六枝特区| 阜新市| 保山市| 珲春市| 定兴县| 固安县| 富锦市| 皋兰县| 阿勒泰市| 称多县| 博野县| 安福县| 章丘市| 贵州省| 龙山县| 勃利县| 十堰市| 稷山县| 台中县| 益阳市| 大埔县| 灵石县| 肇州县| 明水县| 广水市| 仲巴县| 安泽县| 皮山县| 佛教| 德兴市| 威海市| 巴青县| 望城县| 南皮县| 当雄县| 松溪县| 金塔县| 长葛市| 芒康县| 化隆| 乐安县|