golang開發:go併發的建議(完)

来源:https://www.cnblogs.com/feixiangmanon/archive/2022/10/10/16774317.html
-Advertisement-
Play Games

上次說了一下Go語言佈道師 Dave Cheney對Go併發的建議,個人覺得最重要的一條,這次主要想說一下這個。 8.3. Never start a goroutine without knowning when it will stop(永遠不要在不知道何時停止的情況下啟動 goroutine) ...


上次說了一下Go語言佈道師 Dave Cheney對Go併發的建議,個人覺得最重要的一條,這次主要想說一下這個。
8.3. Never start a goroutine without knowning when it will stop(永遠不要在不知道何時停止的情況下啟動 goroutine)

我們的需求

我這邊當時有個需求是這樣的,我們有個考試系統的,每次學員答完試卷去檢查一下這次交卷是否是這次考試的最後一份試卷,如果是最後一份試卷的話,需要計算這次考試的總成績,生成考試的學習報告,當然了,如果不是最後一份試卷的話啥也不幹。
生成試卷和報告是必須要生成的,不能出現考完試了沒有總成績和總報告。
接到這個需求的時候,我首先想到的是使用golang的goroutine去非同步算出成績生成報告。然後寫代碼就是這樣的。

go createReport()

這不剛好是8.3 永遠不要這樣寫的建議麽?
然後覺得應該寫一個管理goroutine非同步執行任務的類庫,創建執行銷毀都由這個管理工具去執行。準備寫的時候發現B站的代碼里有一個這樣的類庫,非同步執行的類庫。

B站的類庫

B站代碼裡面非同步任務是這個文件
openbilibili-go-common-master/library/sync/pipeline/fanout/fanout.go

var (
	// ErrFull chan full.
	ErrFull   = errors.New("fanout: chan full")
	stats     = prom.BusinessInfoCount
	traceTags = []trace.Tag{
		trace.Tag{Key: trace.TagSpanKind, Value: "background"},
		trace.Tag{Key: trace.TagComponent, Value: "sync/pipeline/fanout"},
	}
)

type options struct {
	worker int
	buffer int
}

// Option fanout option
type Option func(*options)

// Worker specifies the worker of fanout
func Worker(n int) Option {
	if n <= 0 {
		panic("fanout: worker should > 0")
	}
	return func(o *options) {
		o.worker = n
	}
}

// Buffer specifies the buffer of fanout
func Buffer(n int) Option {
	if n <= 0 {
		panic("fanout: buffer should > 0")
	}
	return func(o *options) {
		o.buffer = n
	}
}

type item struct {
	f   func(c context.Context)
	ctx context.Context
}

// Fanout async consume data from chan.
type Fanout struct {
	name    string
	ch      chan item
	options *options
	waiter  sync.WaitGroup

	ctx    context.Context
	cancel func()
}

// New new a fanout struct.
func New(name string, opts ...Option) *Fanout {
	if name == "" {
		name = "fanout"
	}
	o := &options{
		worker: 1,
		buffer: 1024,
	}
	for _, op := range opts {
		op(o)
	}
	c := &Fanout{
		ch:      make(chan item, o.buffer),
		name:    name,
		options: o,
	}
	c.ctx, c.cancel = context.WithCancel(context.Background())
	c.waiter.Add(o.worker)
	for i := 0; i < o.worker; i++ {
		go c.proc()
	}
	return c
}

func (c *Fanout) proc() {
	defer c.waiter.Done()
	for {
		select {
		case t := <-c.ch:
			wrapFunc(t.f)(t.ctx)
			stats.State(c.name+"_channel", int64(len(c.ch)))
		case <-c.ctx.Done():
			return
		}
	}
}

func wrapFunc(f func(c context.Context)) (res func(context.Context)) {
	res = func(ctx context.Context) {
		defer func() {
			if r := recover(); r != nil {
				buf := make([]byte, 64*1024)
				buf = buf[:runtime.Stack(buf, false)]
				log.Error("panic in fanout proc, err: %s, stack: %s", r, buf)
			}
		}()
		f(ctx)
		if tr, ok := trace.FromContext(ctx); ok {
			tr.Finish(nil)
		}
	}
	return
}

// Do save a callback func.
func (c *Fanout) Do(ctx context.Context, f func(ctx context.Context)) (err error) {
	if f == nil || c.ctx.Err() != nil {
		return c.ctx.Err()
	}
	nakeCtx := metadata.WithContext(ctx)
	if tr, ok := trace.FromContext(ctx); ok {
		tr = tr.Fork("", "Fanout:Do").SetTag(traceTags...)
		nakeCtx = trace.NewContext(nakeCtx, tr)
	}
	select {
	case c.ch <- item{f: f, ctx: nakeCtx}:
	default:
		err = ErrFull
	}
	stats.State(c.name+"_channel", int64(len(c.ch)))
	return
}

// Close close fanout
func (c *Fanout) Close() error {
	if err := c.ctx.Err(); err != nil {
		return err
	}
	c.cancel()
	c.waiter.Wait()
	return nil
}

使用方法
	ca := New("cache", Worker(100), Buffer(1024))
	var run bool
	ca.Do(context.Background(), func(c context.Context) {
		run = true
	})

主要分析一下這個類庫,以後自己寫或者使用的時候就能得心應手了,而且這個類庫也算是創建goroutine,通過channel通信的經典寫法吧
在這裡插入圖片描述
1.New方法調用的時候,會創建buffer個ch channel,worker個goroutine.由於ch是空的,worker個goroutine會阻塞住,一直等待有程式往ch裡面寫入數據
2.Do函數一但被調用,會傳入非同步任務的func,func就會寫入到ch裡面了,goroutine就可以從ch裡面讀取到數據,並且執行這個數據裡面的func
踐行了這個原則
不要通過共用記憶體來通信,要通過通信來共用記憶體

有個需要註意的點,就Do函數在執行代碼是這樣的
在這裡插入圖片描述
代碼裡面可以看到在c.ch 寫入數據的時候,如果超過c.ch的長度(測試代碼裡面是1024)就報錯返回了,這樣就不能保證每個非同步任務都能穩定執行了,這樣的結果就是,如果程式處理慢或者非同步任務數量比較多的話(超過1024),非同步任務就無法完成。當然了,我們也可以修改代碼改成等待ch的裡面數據被goroutine處理的小於1024了,也會執行,這樣就變成一個不可控的程式了,如果有3000個非同步任務沒人知道執行完成需要多長時間,然後我們程式如果重啟的話,是等待它完成重啟還是強制重啟,等待完成不知道需要等待多長時間,強制重啟就無法保證任務能夠全部完成。

最終方案

為了一定能夠在任何異常情況算出分數和生成報告,最後使用消息隊列做了這件事,發送完成答卷的消息,接收到完成答卷的消息之後算出分數生成報告。做完之後雖然保證了可靠性,但是覺得自己發消息自己收消息確實也很彆扭。
不知道其他童鞋有沒有更好的更合理的方案。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡會介紹ClickHouse幾種資料庫引擎,已經對應的特點和應用的場景。資料庫引擎允許您處理數據表。預設情況下,ClickHouse使用Atomic資料庫引擎。它提供了可配置的table engines和SQL dialect。 目前的資料庫引擎: MySQL MaterializeMySQL L ...
  • 我們平時會寫各種各樣或簡單或複雜的sql語句,提交後就會得到我們想要的結果集。比如sql語句,”select * from t_user where user_id > 10;”,意在從表t_user中篩選出user_id大於10的所有記錄。你有沒有想過從一條sql到一個結果集,這中間經歷了多少坎坷... ...
  • 如何使用KrpanoToolJS在瀏覽器切圖 框架DEMO 框架源碼地址 【獨闢蹊徑】逆推Krpano切圖演算法,實現在瀏覽器切多層級瓦片圖 一、功能介紹 在瀏覽器中將全景圖轉為立方體圖、多層級瓦片圖 備註: 切圖的邏輯、縮略圖、預覽圖均以krpano為標準,如果是使用krpano來開發全景的,可以直 ...
  • 摘要:要想減少迴流和重繪的次數,首先要瞭解迴流和重繪是如何觸發的。 本文分享自華為雲社區《前端頁面之“迴流重繪”》,作者:CoderBin。 “迴流重繪”是什麼? 在HTML中,每個元素都可以理解成一個盒子,在瀏覽器解析過程中,會涉及到迴流與重繪: 迴流:佈局引擎會根據各種樣式計算每個盒子在頁面上的 ...
  • 導讀:成為一名架構師可能是很多開發者的技術追求之一。那麼如何理解架構?架構師是一個什麼樣的角色,需要具備什麼樣的能力?在架構師的道路上,會面臨哪些挑戰?本文作者道延分享他對架構以及架構師的思考和相關實踐,希望對同學們有所啟發。 ...
  • 定義抽象基類,規範介面內部方法執行順序 在進階篇中,沒專門提過抽象基類,在這裡順便就提一下 抽象基類的核心特征:不能被直接實例化 相反,抽象基類和元類一樣,一般都被當做頂層基類使用,派生類必須實現抽象類中指定的方法,且方法名也必須保持一致 抽象基類的主要用途:從一種高層次上規範編程介面 話不多說,直 ...
  • 本文詳細介紹了我國銀行核心系統的定義、位置與邊界,發展歷程、更換核心系統的原因,以及新核心建設的五大模式與其特點對比。希望內容能夠幫助銀行科技從業者建立起對銀行核心系統的整體認知,提供一定積極的指導作用與借鑒意義,從而引發思考並促進行業交流與探討,共同為我國的銀行科技蓬勃發展貢獻自己的智慧與經驗。 ... ...
  • 外觀模式是最常用的結構型設計模式,也是一種非常容易理解的設計模式,其核心就是為多個子系統提供一個統一的介面,將這個介面看作是這些子系統的門面。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...