go高併發之路——go語言如何解決併發問題

来源:https://www.cnblogs.com/lmz-blogs/p/18200946
-Advertisement-
Play Games

一、選擇GO的原因 作為一個後端開發,日常工作中接觸最多的兩門語言就是PHP和GO了。無可否認,PHP確實是最好的語言(手動狗頭哈哈),寫起來真的很舒爽,沒有任何心智負擔,字元串和整型壓根就不用區分,開發速度真的是比GO快很多。現在工作中也還是有一些老項目在使用PHP,但21年之後的新項目基本上就都 ...


一、選擇GO的原因

作為一個後端開發,日常工作中接觸最多的兩門語言就是PHP和GO了。無可否認,PHP確實是最好的語言(手動狗頭哈哈),寫起來真的很舒爽,沒有任何心智負擔,字元串和整型壓根就不用區分,開發速度真的是比GO快很多。現在工作中也還是有一些老項目在使用PHP,但21年之後的新項目基本上就都是用GO了。那為什麼PHP那麼香,還要轉戰使用GO呢,下麵就給大家講解一下我們新項目從PHP轉GO的原因,有幾個比較重要的點:

1、PHP不能滿足我們的高併發業務,這是最主要的原因了,(PS:我這裡所說的PHP是指官方的php-fpm模式下的開發,是一個請求一個進程的那種模式,而不是類似於swoole常駐進程的那種。那麼為什麼不去使用swoole呢,當然也是有的,但swoole畢竟太小眾了,且之前有很多bug,使用起來心智負擔太高了),而我們部門所負責的是直播業務,每天都和高併發打交道啊,所以只能將目光轉向了併發小王子GO的懷抱。

2、GO語言當時在市面上很火,像騰訊、百度、滴滴、好未來這些大廠都在陸陸續續地從PHP轉向GO,這也是一個訊號吧,跟著大佬們走總不會錯。

3、GO語言的簡單簡潔,相比較於JAVA,上手是很快的(但真正學好還是沒那麼容易的),我當時就學了兩個禮拜左右語法就跟著一起寫項目了。

二、GO解決的併發問題

說到併發,是GO最基本的功能了,但是在傳統的PHP中是比較困難的,如果不藉助其它一些擴展的話,是做不到併發的。舉個場景:每個用戶進入直播間,都要獲取很多信息,有版本服務信息、直播基礎信息、用戶信息、直播關聯權益信息、直播間信息統計等等。如果是PHP的寫法,就得按照下麵串列的流程去做,這個介面耗時就是所有操作的時間之和,嚴重影響用戶體驗啊。

但如果換成GO去做這件事,那就非常清爽了,這個用戶請求耗時就只需要時間最長的那個操作耗時,如下圖:

那麼我們如何用去實現這個併發邏輯呢?

方法1:使用sync.WaitGroup

//請求入口
func main() {
	var (
		VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
	)
	ctx := context.Background()
	GoNoErr(ctx, func() {
		VersionDetail = 1 //版本服務信息
		time.Sleep(1 * time.Second)
		fmt.Println("執行第一個任務")
	}, func() {
		LiveDetail = 2 //直播基礎信息
		time.Sleep(2 * time.Second)
		fmt.Println("執行第二個任務")
	}, func() {
		UserDetail = 3 //用戶信息
		time.Sleep(3 * time.Second)
		fmt.Println("執行第三個任務")
	}, func() {
		EquityDetail = 4 //直播關聯權益信息
		time.Sleep(4 * time.Second)
		fmt.Println("執行第四個任務")
	}, func() {
		StatisticsDetail = 5 //直播間信息統計
		time.Sleep(5 * time.Second)
		fmt.Println("執行第五個任務")
	})
	fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
}

//併發方法
func GoNoErr(ctx context.Context, functions ...func()) {
	var wg sync.WaitGroup
	for _, f := range functions {
		wg.Add(1)
		// 每個函數啟動一個協程
		go func(function func()) {
			function()
			wg.Done()
		}(f)
	}
	// 等待執行完
	wg.Wait()
}

方法2:使用ErrGroup庫

//請求入口
func main() {
	var (
		VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
		err                                                                   error
	)
	ctx := context.Background()
	err = GoErr(ctx, func() error {
		VersionDetail = 1 //版本服務信息
		time.Sleep(1 * time.Second)
		fmt.Println("執行第一個任務")
		return nil //返回實際執行的錯誤
	}, func() error {
		LiveDetail = 2 //直播基礎信息
		time.Sleep(2 * time.Second)
		fmt.Println("執行第二個任務")
		return nil //返回實際執行的錯誤
	}, func() error {
		UserDetail = 3 //用戶信息
		time.Sleep(3 * time.Second)
		fmt.Println("執行第三個任務")
		return nil //返回實際執行的錯誤
	}, func() error {
		EquityDetail = 4 //直播關聯權益信息
		time.Sleep(4 * time.Second)
		fmt.Println("執行第四個任務")
		return nil //返回實際執行的錯誤
	}, func() error {
		StatisticsDetail = 5 //直播間信息統計
		time.Sleep(5 * time.Second)
		fmt.Println("執行第五個任務")
		return nil //返回實際執行的錯誤
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)

}

func GoErr(ctx context.Context, functions ...func() error) error {
	var eg errgroup.Group
	for i := range functions { 
		f := functions[i]  //請註意這裡的寫法,下麵有講解
		eg.Go(func() (err error) {
			err = f()
			if err != nil {
				//記日誌
			}
			return err
		})
	}
	// 等待執行完
	return eg.Wait()
}

上面就是使用ErrGroup庫的併發執行任務的方法,可以直接拿來使用,ErrGroup這是GO官方提供的一個同步擴展庫可以很好地將⼀個通⽤的⽗任務拆成⼏個⼩任務併發執⾏

上面有一點需要特別註意的寫法,就是下麵這段代碼的寫法,寫法1:

for i := range functions { 
		f := functions[i]  
		eg.Go(func() (err error) {
			err = f()

也可以這樣寫,寫法2:

for _, f := range functions { 
		fs := f  
		eg.Go(func() (err error) {
			err = fs()

但如果這樣寫就會有問題,寫法3:

for _, f := range functions { 
		eg.Go(func() (err error) {
			err = f()

你們可以改一下,實際跑一下。會發現 (寫法3) 會出現類似這樣的錯誤結果

正確預期的結果(寫法1、寫法2)應該是這樣的

這是因為在 Go 語言中,當使用閉包(匿名函數)時,如果閉包引用了外部的變數,閉包實際上會捕獲這些變數的引用。在迴圈中創建閉包時,如果直接將迴圈變數作為閉包的參數或在閉包中引用該變數,會導致所有生成的閉包都引用相同的變數,即最後一次迭代的值。

為了避免這個問題,常見的做法是在迴圈內部創建一個新的變數,將迴圈變數的值賦給這個新變數,然後在閉包中引用該新變數。這樣,每次迴圈迭代都會創建一個新的變數,閉包捕獲的是不同的變數引用,而不是相同變數的引用。

在給定的代碼中,fs := f 就是為了創建一個新的變數 f,並將迴圈變數 f 的值賦給它。這樣,在閉包中就可以安全地引用這個新變數 f,而不會受到迴圈迭代的影響。這個技巧非常有用,可以在迴圈中創建多個獨立的閉包,並確保它們捕獲的是預期的變數值,而不會受到迴圈迭代的干擾

當然,還有一些第三方庫也實現了上面的併發分組操作,大家感興趣的可以去GitHub上看看,但功能和實現基本都大同小異。以上就是GO併發的基礎,將一個父任務拆分成多個子任務去執行,提高程式的併發度,節省程式耗時。我們平時在工作中,兩種方法都可以直接拿來使用,可以說這兩個GO併發方法幾乎貫穿了我的GO職業生涯,也是最基礎最實用的併發操作方法

一個人可以被毀滅,但不可以被打敗。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • PPT是傳遞信息、進行彙報和推廣產品的重要工具。然而,有時我們需要將這些精心設計的PPT演示文稿發佈到網路上,以便於更廣泛的訪問和分享。本文將介紹如何使用Python將PowerPoint文檔轉換為網頁友好的HTML格式。包含兩個簡單示例: Python 將PowerPoint文檔轉為HTML格式 ...
  • 項目基於 Spring Boot 3.2.5 Pom 需要註意的是,引用 Mybatis-Plus 依賴,無需手動引入 Mybatis <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j --> <dependenc ...
  • 簡介 waynboot-mall 是一套全部開源的 H5 商城項目,包含運營後臺、H5 商城前臺和後端介面三個項目 。實現了一套完整的商城業務,有首頁展示、商品分類、商品詳情、sku 詳情、商品搜索、加入購物車、結算下單、支付寶/微信支付/易支付對接、我的訂單列表、商品評論等一系列功能 。 ...
  • 主要用於去除圖片的白邊和黑邊,比如在截圖表情包的時候,通過小米的傳送門保存圖片的時候,圖片往往會有黑邊和白邊,此時使用此腳本二次處理 import os from PIL import Image, ImageChops def trim_white_border(image): bg = Imag ...
  • novel —— 一套基於 Spring Boot3 + Vue3 開發的前後端分離學習型小說項目。由小說門戶系統、作家後臺管理系統、平臺後臺管理系統等多個子系統構成。 ...
  • 1. Spring 對於事務上的應用的詳細說明 @目錄1. Spring 對於事務上的應用的詳細說明每博一文案2. 事務概述3. 引入事務場景3.1 第一步:準備資料庫表3.2 第二步:創建包結構3.3 第三步:準備對應資料庫映射的 Bean 類3.4 第四步:編寫持久層3.5 第五步:編寫業務層3 ...
  • APCu 極簡概括: PHP 的開源記憶體緩存擴展,類比Redis,但是一般都用Redis,所以APCu用的很少。 官方文檔:https://www.php.net/manual/zh/apcu.configuration.php 解決問題:類比Redis做緩存組件,提升性能,同步數據使用。 適用場景 ...
  • nginx 系列 Nginx-01-聊一聊 nginx Nginx-01-Nginx 是什麼 Nginx-02-為什麼使用 Nginx Nginx-02-Nginx Ubuntu 安裝 + windows10 + WSL ubuntu 安裝 nginx 實戰筆記 Nginx-02-基本使用 Ngin ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...