欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語(yǔ)言中處理并發(fā)錯(cuò)誤的常用方法總結(jié)

 更新時(shí)間:2025年06月25日 09:06:06   作者:左詩(shī)右碼  
在Go語(yǔ)言中,錯(cuò)誤處理一直是開(kāi)發(fā)中不可或缺的一部分,尤其在并發(fā)編程場(chǎng)景下,由于多個(gè)goroutine同時(shí)運(yùn)行,錯(cuò)誤的傳遞和處理就變得更為復(fù)雜,這篇文章就介紹了一些常見(jiàn)的處理并發(fā)錯(cuò)誤的方法,以供各位參考

一、 panic 只會(huì)觸發(fā)當(dāng)前 goroutine 中的 defer 操作

很多開(kāi)發(fā)者初次接觸 Go 時(shí)容易誤解 panic 的作用范圍。下面我們先來(lái)看一個(gè)錯(cuò)誤的代碼示例:

1.1 示例代碼

package main

import (
	"fmt"
	"time"
)

func main() {
	// 在主 goroutine 中設(shè)置 defer,用于捕獲 panic
	// 注意:這個(gè) defer 只能捕獲發(fā)生在主 goroutine 中的 panic
	defer func() {
		// recover() 只能捕獲當(dāng)前 goroutine 內(nèi)的 panic,
		// 如果 panic 發(fā)生在其他 goroutine 中,該 defer 無(wú)法捕獲
		if e := recover(); e != nil {
			fmt.Println("捕獲到 panic:", e)
		}
	}()

	// 啟動(dòng)子 goroutine,演示 panic 的傳播范圍
	go func() {
		// 輸出提示信息,表示子 goroutine 開(kāi)始執(zhí)行
		fmt.Println("子 goroutine 開(kāi)始")
		// 主動(dòng)觸發(fā) panic,注意這里的 panic 發(fā)生在子 goroutine 內(nèi),
		// 因此主 goroutine 中的 defer 無(wú)法捕獲該 panic
		panic("Goroutine 發(fā)生 panic")
	}()

	// 主 goroutine 等待一段時(shí)間,確保子 goroutine 有足夠時(shí)間執(zhí)行
	time.Sleep(2 * time.Second)
	// 輸出主 goroutine 結(jié)束信息
	fmt.Println("主 goroutine 結(jié)束")
}

運(yùn)行這段代碼,我們會(huì)發(fā)現(xiàn),會(huì)直接報(bào)錯(cuò)了:

子 goroutine 開(kāi)始
panic: Goroutine 發(fā)生 panic

goroutine 18 [running]:
main.main.func2()
        ~/golang-tutorial/tt.go:25 +0x59
created by main.main in goroutine 1
        ~/golang-tutorial/tt.go:20 +0x3b
exit status 2

1.2 代碼說(shuō)明

  • 主 goroutine 中的 defer: 主函數(shù)開(kāi)始時(shí)設(shè)置了一個(gè) defer 函數(shù),目的是在發(fā)生 panic 時(shí)捕獲并打印錯(cuò)誤信息。然而,由于 recover 只能捕獲當(dāng)前 goroutine 內(nèi)的 panic,當(dāng)子 goroutine 內(nèi)發(fā)生 panic 時(shí),這個(gè) defer 不會(huì)生效。
  • 子 goroutine 中的 panic: 在子 goroutine 中調(diào)用 panic 后,由于沒(méi)有設(shè)置獨(dú)立的 recover 邏輯,該 goroutine 會(huì)直接崩潰,panic 信息不會(huì)傳遞到主 goroutine 中。 這樣可以清楚地看到,即使主 goroutine 使用了 defer 進(jìn)行錯(cuò)誤捕獲,也無(wú)法捕捉到其他 goroutine 中發(fā)生的 panic。
  • 延時(shí)等待: 主 goroutine 使用 time.Sleep 等待一定時(shí)間,以確保子 goroutine 有機(jī)會(huì)執(zhí)行并觸發(fā) panic,從而驗(yàn)證 panic 的作用范圍。

既然程序會(huì)直接崩潰,那么,如何解決這個(gè)問(wèn)題呢?

1.3 正確處理

我們只需要在子 goroutine 中使用 recover 就可以了:

package main

import (
	"fmt"
	"time"
)

func main() {
	defer func() {
		if e := recover(); e != nil {
			fmt.Println("捕獲到 panic:", e)
		}
	}()

	go func() {
		defer func() {
			if e := recover(); e != nil {
				fmt.Println("子 goroutine 捕獲到 panic:", e)
			}
		}()
		fmt.Println("子 goroutine 開(kāi)始")
		panic("Goroutine 發(fā)生 panic")
	}()

	time.Sleep(2 * time.Second)
	fmt.Println("主 goroutine 結(jié)束")
}

運(yùn)行以上代碼,可以發(fā)現(xiàn),打印出的結(jié)果為:

子 goroutine 開(kāi)始
子 goroutine 捕獲到 panic: Goroutine 發(fā)生 panic
主 goroutine 結(jié)束

這就說(shuō)明:panic 只會(huì)觸發(fā)當(dāng)前 goroutine 內(nèi)的 defer 操作,不能跨 goroutine 捕獲或恢復(fù)其他 goroutine 中的 panic。

二、多 goroutine 中收集錯(cuò)誤和結(jié)果

假設(shè)我們有個(gè)需求,需要同時(shí)使用多個(gè) goroutine 通過(guò) http.Get 去請(qǐng)求以下四個(gè)地址,其中只有 https://httpbin.org/get 能夠正常響應(yīng),其余地址均為故意寫(xiě)錯(cuò)的地址:

  • https://httpbin1.org/get
  • https://httpbin.org/get
  • https://httpbin2.org/get
  • https://httpbin3.org/get

2.1 如何批量收集錯(cuò)誤信息?

在并發(fā)請(qǐng)求中,可以通過(guò)錯(cuò)誤通道( error channel )來(lái)收集各個(gè) goroutine 中發(fā)生的錯(cuò)誤。例如:

package main

import (
	"fmt"
	"net/http"
	"sync"
)

func main() {
	urls := []string{
		"https://httpbin1.org/get",
		"https://httpbin.org/get",
		"https://httpbin2.org/get",
		"https://httpbin3.org/get",
	}

	var wg sync.WaitGroup
	// 創(chuàng)建一個(gè)帶緩沖的錯(cuò)誤通道,大小為 URL 數(shù)量
	errCh := make(chan error, len(urls))

	// 遍歷所有 URL,分別啟動(dòng) goroutine 發(fā)起請(qǐng)求
	for _, url := range urls {
		wg.Add(1)
		go func(url string) {
			defer wg.Done() // 保證 goroutine 結(jié)束時(shí)減少計(jì)數(shù)
			resp, err := http.Get(url)
			if err != nil {
				// 如果請(qǐng)求出錯(cuò),將錯(cuò)誤發(fā)送到錯(cuò)誤通道中
				errCh <- fmt.Errorf("請(qǐng)求 %s 失?。?%v", url, err)
				return
			}
			defer resp.Body.Close()
			// 打印成功信息
			fmt.Printf("請(qǐng)求 %s 成功,狀態(tài)碼: %d\n", url, resp.StatusCode)
		}(url)
	}

	// 等待所有 goroutine 執(zhí)行完畢
	wg.Wait()
	// 關(guān)閉錯(cuò)誤通道
	close(errCh)

	// 遍歷錯(cuò)誤通道,輸出所有錯(cuò)誤信息
	for err := range errCh {
		fmt.Println("錯(cuò)誤信息:", err)
	}
}

在這個(gè)示例中,我們通過(guò)一個(gè) channel errCh 來(lái)存儲(chǔ)每個(gè) goroutine 產(chǎn)生的錯(cuò)誤,待所有 goroutine 執(zhí)行完畢后,再統(tǒng)一處理錯(cuò)誤信息。

2.2 那如果也需要結(jié)果呢?

如果希望每個(gè)請(qǐng)求的結(jié)果和可能的錯(cuò)誤信息,我們可以定義一個(gè)結(jié)構(gòu)體,將請(qǐng)求的結(jié)果與錯(cuò)誤信息封裝在一起,再通過(guò) channel 收集:

package main

import (
	"fmt"
	"io"
	"net/http"
	"sync"
)

// Result 用于封裝每個(gè)請(qǐng)求的結(jié)果和錯(cuò)誤信息
type Result struct {
	URL        string // 請(qǐng)求的 URL
	StatusCode int    // 返回的 HTTP 狀態(tài)碼
	Err        error  // 請(qǐng)求過(guò)程中發(fā)生的錯(cuò)誤
	Content    []byte // 返回的內(nèi)容
}

func main() {
	urls := []string{
		"https://httpbin1.org/get",
		"https://httpbin.org/get",
		"https://httpbin2.org/get",
		"https://httpbin3.org/get",
	}

	var wg sync.WaitGroup
	// 創(chuàng)建帶緩沖的結(jié)果通道,大小為 URL 數(shù)量
	resCh := make(chan Result, len(urls))

	// 遍歷 URL,啟動(dòng) goroutine 進(jìn)行請(qǐng)求
	for _, url := range urls {
		wg.Add(1)
		go func(url string) {
			defer wg.Done()
			resp, err := http.Get(url)

			result := Result{URL: url}

			if err != nil {
				// 將錯(cuò)誤結(jié)果封裝后發(fā)送到結(jié)果通道
				result.Err = err
			} else {
				defer resp.Body.Close()
				body, _ := io.ReadAll(resp.Body)
				// 將成功的結(jié)果封裝后發(fā)送到結(jié)果通道
				result.StatusCode = resp.StatusCode
				result.Content = body
			}

			resCh <- result
		}(url)
	}

	// 等待所有 goroutine 執(zhí)行完畢
	wg.Wait()
	close(resCh)

	// 遍歷結(jié)果通道,輸出每個(gè)請(qǐng)求的結(jié)果和錯(cuò)誤信息
	for res := range resCh {
		if res.Err != nil {
			fmt.Printf("請(qǐng)求 %s 失敗: %v\n", res.URL, res.Err)
		} else {
			fmt.Printf("請(qǐng)求 %s 成功,狀態(tài)碼: %d, 內(nèi)容: %s \n", res.URL, res.StatusCode, string(res.Content))
		}
	}
}

在這個(gè)示例中,每個(gè) goroutine 都會(huì)將自己的請(qǐng)求結(jié)果封裝到 Result 結(jié)構(gòu)體中,通過(guò)通道傳遞回來(lái),最后我們可以一一對(duì)應(yīng)地輸出結(jié)果和錯(cuò)誤信息。

三、 errgroup 包

3.1 errgroup 包簡(jiǎn)介

golang.org/x/sync/errgroup 包提供了一個(gè)便捷的方式來(lái)管理一組 goroutine,并能統(tǒng)一收集它們產(chǎn)生的錯(cuò)誤。該包的主要功能有:

  • 錯(cuò)誤收集與聚合: 當(dāng)多個(gè) goroutine 發(fā)生錯(cuò)誤時(shí),errgroup 會(huì)返回第一個(gè)遇到的錯(cuò)誤。
  • 自動(dòng)等待: 調(diào)用 g.Wait() 可以等待所有啟動(dòng)的 goroutine 執(zhí)行完畢。
  • 與 context 結(jié)合: 通過(guò) WithContext 方法,可以為所有 goroutine 傳入相同的 context,從而實(shí)現(xiàn)統(tǒng)一的取消邏輯。

這些特性使得 errgroup 在需要并發(fā)執(zhí)行多個(gè)任務(wù)且統(tǒng)一管理錯(cuò)誤時(shí)非常有用。

3.2 用 errgroup 包實(shí)戰(zhàn)一下

以下示例演示了如何使用 errgroup 包來(lái)并發(fā)請(qǐng)求多個(gè) URL:

package main

import (
	"fmt"
	"net/http"

	"golang.org/x/sync/errgroup"
)

func main() {
	urls := []string{
		"https://httpbin1.org/get",
		"https://httpbin.org/get",
		"https://httpbin2.org/get",
		"https://httpbin3.org/get",
	}

	// 定義一個(gè)存儲(chǔ)結(jié)果的切片,與 errgroup 共同使用
	results := make([]string, len(urls))
	var g errgroup.Group

	// 遍歷所有 URL,啟動(dòng) goroutine 執(zhí)行 HTTP 請(qǐng)求
	for i, url := range urls {
		i, url := i, url // 為了避免閉包引用同一個(gè)變量
		g.Go(func() error {
			fmt.Println("開(kāi)始請(qǐng)求:", url)
			resp, err := http.Get(url)
			if err != nil {
				return fmt.Errorf("請(qǐng)求 %s 失?。?%v", url, err)
			}
			defer resp.Body.Close()
			results[i] = fmt.Sprintf("請(qǐng)求 %s 成功,狀態(tài)碼: %d", url, resp.StatusCode)
			return nil
		})
	}

	// 等待所有 goroutine 執(zhí)行完畢
	if err := g.Wait(); err != nil {
		fmt.Println("發(fā)生錯(cuò)誤:", err)
	}

	// 輸出所有請(qǐng)求成功的結(jié)果
	for _, res := range results {
		fmt.Println(res)
	}
}

通過(guò)運(yùn)行上面的代碼,可能會(huì)打印出類似以下內(nèi)容:

開(kāi)始請(qǐng)求: https://httpbin3.org/get
開(kāi)始請(qǐng)求: https://httpbin2.org/get
開(kāi)始請(qǐng)求: https://httpbin1.org/get
開(kāi)始請(qǐng)求: https://httpbin.org/get
發(fā)生錯(cuò)誤: 請(qǐng)求 https://httpbin3.org/get 失?。?Get "https://httpbin3.org/get": dial tcp: lookup httpbin3.org: no such host

請(qǐng)求 https://httpbin.org/get 成功,狀態(tài)碼: 200

我們可以得出以下重要的結(jié)論:Wait 會(huì)阻塞直至由上述 Go 方法調(diào)用的所有函數(shù)都返回,但是,如果有錯(cuò)誤的話,只會(huì)記錄第一個(gè)非 nil 的錯(cuò)誤,也就是說(shuō),如果有多個(gè)錯(cuò)誤的情況下,不會(huì)收集所有的錯(cuò)誤。

并且,通過(guò)源碼得知:當(dāng)遇到第一個(gè)錯(cuò)誤時(shí),如果之前設(shè)定了 cancel 方法,那么還會(huì)調(diào)用 cancel 方法,那么,如何創(chuàng)建帶有 cancel 方法的 errgroup.Group 呢?

3.3 使用 errgroup 包中的 WithContext 方法

有時(shí)我們希望在某個(gè) goroutine 發(fā)生錯(cuò)誤時(shí),能夠通知其他正在執(zhí)行的任務(wù)提前取消。這時(shí)可以使用 errgroup.WithContext 方法。以下示例展示了如何實(shí)現(xiàn)這一點(diǎn):

package main

import (
	"context"
	"fmt"
	"net/http"

	"golang.org/x/sync/errgroup"
)

func main() {
	urls := []string{
		"https://httpbin1.org/get",
		"https://httpbin.org/get",
		"https://httpbin2.org/get",
		"https://httpbin3.org/get",
	}

	// 使用 context.Background 創(chuàng)建基本上下文,并通過(guò) WithContext 包裝 errgroup
	ctx := context.Background()
	g, ctx := errgroup.WithContext(ctx)

	// 定義存儲(chǔ)結(jié)果的切片
	results := make([]string, len(urls))

	// 遍歷所有 URL,啟動(dòng) goroutine 發(fā)起請(qǐng)求
	for i, url := range urls {
		i, url := i, url
		g.Go(func() error {
			fmt.Println("開(kāi)始請(qǐng)求:", url)
			// 在發(fā)起請(qǐng)求前,根據(jù) context 判斷是否取消
			req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
			if err != nil {
				return err
			}

			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				return fmt.Errorf("請(qǐng)求 %s 失敗: %v", url, err)
			}
			defer resp.Body.Close()

			results[i] = fmt.Sprintf("請(qǐng)求 %s 成功,狀態(tài)碼: %d", url, resp.StatusCode)
			return nil
		})
	}

	// 如果有任一任務(wù)返回錯(cuò)誤,將自動(dòng)取消所有依賴于 ctx 的請(qǐng)求
	if err := g.Wait(); err != nil {
		fmt.Println("錯(cuò)誤發(fā)生:", err)
	}

	for _, res := range results {
		fmt.Println(res)
	}
}

運(yùn)行以上的代碼,打印結(jié)果如下:

開(kāi)始請(qǐng)求: https://httpbin3.org/get
開(kāi)始請(qǐng)求: https://httpbin.org/get
開(kāi)始請(qǐng)求: https://httpbin2.org/get
開(kāi)始請(qǐng)求: https://httpbin1.org/get
錯(cuò)誤發(fā)生: 請(qǐng)求 https://httpbin1.org/get 失?。?Get "https://httpbin1.org/get": dial tcp: lookup httpbin1.org: no such host

在這個(gè)示例中,我們使用 errgroup.WithContext 創(chuàng)建了一個(gè)共享的上下文 ctx,所有的 HTTP 請(qǐng)求都與此 context 綁定。一旦某個(gè)請(qǐng)求發(fā)生錯(cuò)誤并返回,其他 goroutine 中綁定該 context 的請(qǐng)求會(huì)立即收到取消信號(hào),從而實(shí)現(xiàn)整體任務(wù)的協(xié)同取消。

四、總結(jié)

本文從以下幾個(gè)方面詳細(xì)介紹了在 Go 語(yǔ)言中如何處理并發(fā)錯(cuò)誤:

  • panic 和 defer: 通過(guò)示例說(shuō)明 panic 只會(huì)觸發(fā)當(dāng)前 goroutine 內(nèi)的 defer 操作,并展示了即使主 goroutine 設(shè)置了 defer,也無(wú)法捕獲子 goroutine 內(nèi)的 panic。
  • 并發(fā)中錯(cuò)誤收集: 通過(guò)簡(jiǎn)單示例展示了如何在多個(gè) goroutine 中分別收集錯(cuò)誤信息,以及如何關(guān)聯(lián)請(qǐng)求結(jié)果與錯(cuò)誤信息。
  • errgroup 包的使用: 介紹了 errgroup 包的核心功能,展示了如何用 errgroup 包簡(jiǎn)化并發(fā)錯(cuò)誤處理,同時(shí)詳細(xì)演示了 WithContext 方法的使用場(chǎng)景和效果。

通過(guò)這些示例和詳細(xì)解釋,希望大家在實(shí)際開(kāi)發(fā)中能夠更加自信地處理并發(fā)任務(wù)中的錯(cuò)誤問(wèn)題,從而編寫(xiě)出更加健壯和易維護(hù)的代碼。

以上就是Go語(yǔ)言中處理并發(fā)錯(cuò)誤的常用方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Go并發(fā)錯(cuò)誤處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作

    以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作

    這篇文章主要介紹了以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Go語(yǔ)言常見(jiàn)錯(cuò)誤接口污染解決分析

    Go語(yǔ)言常見(jiàn)錯(cuò)誤接口污染解決分析

    這篇文章主要為大家介紹了Go語(yǔ)言常見(jiàn)錯(cuò)誤接口污染解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • 利用Go實(shí)現(xiàn)一個(gè)簡(jiǎn)易DAG服務(wù)的示例代碼

    利用Go實(shí)現(xiàn)一個(gè)簡(jiǎn)易DAG服務(wù)的示例代碼

    DAG的全稱是Directed Acyclic Graph,即有向無(wú)環(huán)圖,DAG廣泛應(yīng)用于表示具有方向性依賴關(guān)系的數(shù)據(jù),如任務(wù)調(diào)度、數(shù)據(jù)處理流程、項(xiàng)目管理以及許多其他領(lǐng)域,下面,我將用Go語(yǔ)言示范如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的DAG服務(wù),需要的朋友可以參考下
    2024-03-03
  • golang服務(wù)報(bào)錯(cuò):?write:?broken?pipe的解決方案

    golang服務(wù)報(bào)錯(cuò):?write:?broken?pipe的解決方案

    在開(kāi)發(fā)在線客服系統(tǒng)的時(shí)候,看到日志里有一些錯(cuò)誤信息,下面這篇文章主要給大家介紹了關(guān)于golang服務(wù)報(bào)錯(cuò):?write:?broken?pipe的解決方案,需要的朋友可以參考下
    2022-09-09
  • go語(yǔ)言接口之接口值舉例詳解

    go語(yǔ)言接口之接口值舉例詳解

    接口是一種抽象類型,是對(duì)其他類型行為的概括與抽象,從語(yǔ)法角度來(lái)看,接口是一組方法定義的集合,下面這篇文章主要給大家介紹了關(guān)于go語(yǔ)言接口之接口值的相關(guān)資料,文章通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • Go語(yǔ)言中的iota關(guān)鍵字的使用

    Go語(yǔ)言中的iota關(guān)鍵字的使用

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的iota關(guān)鍵字的相關(guān)使用,文中的示例代碼講解詳細(xì),對(duì)我們深入了解Go語(yǔ)言有一定的幫助,需要的可以參考下
    2023-08-08
  • Golang中Gin框架中如何定義路由詳解

    Golang中Gin框架中如何定義路由詳解

    Gin是一個(gè)用Go語(yǔ)言編寫(xiě)的Web框架,具有高性能和易于使用的特點(diǎn),本文將結(jié)合實(shí)際案例,詳細(xì)介紹Gin框架的路由用法,有需要的小伙伴可以參考下
    2024-10-10
  • go如何使用gin結(jié)合jwt做登錄功能簡(jiǎn)單示例

    go如何使用gin結(jié)合jwt做登錄功能簡(jiǎn)單示例

    jwt全稱Json web token,是一種認(rèn)證和信息交流的工具,這篇文章主要給大家介紹了關(guān)于go如何使用gin結(jié)合jwt做登錄功能的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • 深入解析Go template模板使用詳解

    深入解析Go template模板使用詳解

    這篇文章主要介紹了深入解析Go template模板使用詳解,需要的朋友可以參考下
    2022-04-04
  • Go語(yǔ)言實(shí)現(xiàn)單端口轉(zhuǎn)發(fā)到多個(gè)端口

    Go語(yǔ)言實(shí)現(xiàn)單端口轉(zhuǎn)發(fā)到多個(gè)端口

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)單端口轉(zhuǎn)發(fā)到多個(gè)端口,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的小伙伴可以了解下
    2024-02-02

最新評(píng)論