go-GMP 協程切換時機 和 協程實戰

来源:https://www.cnblogs.com/studyios/archive/2023/11/30/17868145.html
-Advertisement-
Play Games

Sun公司提供了JavaMail用來實現郵件發送,但是配置煩瑣,Spring中提供了JavaMailSender用來簡化郵件配置,Spring Boot則提供了MailSenderAutoConfiguration對郵件的發送做了進一步簡化。 v準備工作 開通POP3/SMTP服務或者IMAP/SM ...


當m在執行某個g的時候,g非常耗時,例如一個for迴圈,每次迴圈sleep1分鐘,迴圈1000次。

這個例子看似無聊,卻是很難解決的,成功的避開了2個系統切換時機。

如果這個時候,一直執行這個g,別的g就會得不到執行,例如有g是處理用戶支付的,這樣就會造成收錢不積極。

協程饑餓問題

本地隊列

本地隊列因為 某個G一直 占著M,導致其他G無法執行。

如果占用時間過長的這個G,能讓出來M,讓別的G也能執行,本地隊列迴圈的著執行,就能解決這個問題。

全局隊列

除了本地隊列,全局隊列也會有這個問題,如果一個新創建的g,放在全局隊列中,而現有的p的本地隊列都未執行完,則全局隊列需要排隊很久。

解決辦法,每過一段時間,每個本地隊列都先來全局隊列中取1個,這樣就能解決這個問題。

代碼實現:

又到了findRunnable()

// Check the global runnable queue once in a while to ensure fairness.
if pp.schedtick%61 == 0 && sched.runqsize > 0 {
	lock(&sched.lock)
	gp := globrunqget(pp, 1)
	unlock(&sched.lock)
	if gp != nil {
		return gp, false, false
	}
}

這個優先順序在 本地隊列之前。之前看過 globrunqget()中的邏輯,當max為1時候,就只會取一個。

每61次,就去全局隊列中拿一個。

解決辦法

協程因為獨特的數據結構,能能夠暫停的,之前協程的本質有介紹過,暫停後,讓別的g也開始迴圈執行。

切換時機

主動掛起

業務方法主動調用gopark 然後,切換協程。

  源碼在proc.go中
// Puts the current goroutine into a waiting state and calls unlockf on the
// 讓當前的 g 進入 waiting的狀態
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceReason traceBlockReason, traceskip int) {
	if reason != waitReasonSleep {
		checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
	}
	mp := acquirem()
	gp := mp.curg
	status := readgstatus(gp)
	if status != _Grunning && status != _Gscanrunning {
		throw("gopark: bad g status")
	}
	mp.waitlock = lock
	mp.waitunlockf = unlockf
	gp.waitreason = reason
	mp.waitTraceBlockReason = traceReason
	mp.waitTraceSkip = traceskip
	releasem(mp)
	// can't do anything that might move the G between Ms here.
	mcall(park_m) // 切換到了 g0棧,前面講過mcall
}

// park continuation on g0.
func park_m(gp *g) {
	mp := getg().m
	// 中間還有代碼
	if fn := mp.waitunlockf; fn != nil {
		ok := fn(gp, mp.waitlock)
		mp.waitunlockf = nil
		mp.waitlock = nil
		if !ok {
			if traceEnabled() {
				traceGoUnpark(gp, 2)
			}
			casgstatus(gp, _Gwaiting, _Grunnable)
			execute(gp, true) // Schedule it back, never returns.
		}
	}
      schedule() //調了這個方法,之前講過,一旦調用這個方法,就會給m找新的g
}

有個問題:gapark是小寫的,程式員在編碼中是使用不了的,那怎麼讓業務主動調用?

系統runtime裡面很多方法有去調用,例如 time.Sleep、channel的等待等

系統調用完成時

在進行一些系統調用後,例如網路請求等會主動去調這個 exitsyscall() 這個方法,這個方法也會最終走到 schedule()

這個源碼在 syscall_aix.go中,因為有好幾層函數調用,就不貼出來了。

標記搶占 基於 morestack

 1. 系統監控到 Goroutine 運行超過 10ms
 2. 將 g.stackguard0 置為 Oxfffffade  

morestack() 方法,在函數跳轉時候,會自動調用,本意是檢測下新的函數,有沒有足夠的棧空間。

系統在這個函數中,去做了一部分協程切換的,防止一些 耗時比較久的協程,不去觸發上面兩種方案。

看下源碼:

// Called during function prolog when more stack is needed.
// record an argument size. For that purpose, it has no arguments.
TEXT runtime·morestack(SB),NOSPLIT,$0-0

  // 中間很多擴充棧空間的代碼
    CALL	runtime·newstack(SB) // 跟進這個方法
	CALL	runtime·abort(SB)	// crash if newstack returns
	RET


// Goroutine preemption request.
// 0xfffffade in hex.
stackPreempt = uintptrMask & -1314

func newstack() {
 // 中間還有很多源碼
  // 搶占標記   如果g的.stackguard0 欄位被標記為搶占,就會觸發下麵的邏輯
  stackguard0 := atomic.Loaduintptr(&gp.stackguard0)
  preempt := stackguard0 == stackPreempt 
  if preempt {
        // Act like goroutine called runtime.Gosched.
        gopreempt_m(gp) // never return
      }
}

 func gopreempt_m(gp *g) {
  	if traceEnabled() {
  		traceGoPreempt()
  	}
  	goschedImpl(gp)
  }
  
 func goschedImpl(gp *g) {
	//刪了一些源碼,最終調了 schedule 
	schedule()
}

基於信號的搶占標記

開頭那個 for 迴圈的例子,雖然很無聊,但是去避開了上面那種方案,
1. 不會調用 gopark
2. 不會系統調用
3.不會調用 morestack,因為沒有函數調用

這時候,可以使用 信號 來觸發協程的切換。

信號量可以在多線程和多進程直接進行通信(管道、共用記憶體、信號、消息隊列一般作為多進程通信方式)。

原理:

操作系統中,有很多基於信號的底層通信方式,例如: SIGPIPE / SIGURG / SIGHUP
線程可以註冊對應信號的處理函數

go的實現流程:

註冊 `SIGURG`信號的處理函數

`GC`工作時,向目標線程發送信號

線程收到信號,觸發調度,`gc`發送 `sigurg` 觸發`runtime`的 `doSigPreempt()`

能夠猜到 doSigPreempt(), 最終會去調 schedule() 方法。 源碼就不貼了。

開發中,協程過多的問題

  1. 文件打開數限制
      過多協程調用文件讀寫,會操作系統崩潰。

  2. 記憶體限制
      過多協程創建,達到了記憶體的限制  
  
  3. 調度開銷過大
      過多協程,導致調度器調度複雜度增大

解決辦法:

    1. 優化業務邏輯

    2. 利用 channel 的緩存區

    3. 協程池

    4. 調整系統資源

2和3都是從控制協程的數量入手,2適合 單個業務場景,3適合全局。

1和4好理解

利用 channel 的緩存區

func do(c chan interface{}) {

	fmt.Println("do it")
	<-c
}

func main() {
    
  // 利用channel的特性來控制 go協程的個數
	ch := make(chan interface{}, 100)
	for {
		ch <- struct{}{}
		go do(ch)
	}
}

這種適合在一個場景下適用,不推薦全局使用。

協程池

代表:https://github.com/Jeffail/tunny

原理:類似某些語言的線程池

    1.預創建一定數量的協程
    2.將任務送入協程池隊列
    3.協程池不斷取出可用協程,執行任務

慎用協程池

Go語言的線程,已經相當於池化了

二級池化會增加系統複雜度

Go語言的初衷是希望協程即用即毀,不要池化

到此,go的GMP完結。


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

-Advertisement-
Play Games
更多相關文章
  • 【whistle 官網】http://wproxy.org/whistle/install.html 【用途】 抓包、mock、修改返回數據、修改響應頭欄位、延遲模擬弱網等 代理轉發 - 需要配置代理轉發規則 把某環境僅前端資源的請求代理轉發到本地 把某個介面地址的請求代理轉發到指定的後端環境地址 ...
  • 對象類型通常使用interface聲明,可以設置屬性為可選的或者只讀的,可以設置索引簽名。從簡單類型生成複雜類型可以使用類型繼承或者交集類型。提高類型的泛用性可以使用泛型。 ...
  • TS中的函數需要聲明參數列表和返回值的類型,除此只要,還有關於泛型、可選參數、不定長參數列表、回調函數、this、重載的聲明規則。 ...
  • typora-copy-images-to: media 一、數學處理 1、Math常用API 圓周率 Math.PI // 3.1415926535 生成隨機數 Math.random() 生成的是0~1之間的隨機小數,通常在實際項目中需要獲取到一個範圍內的隨機整數,利用這個隨機小數封裝一個獲取範 ...
  • 公眾號「架構成長指南」,專註於生產實踐、雲原生、分散式系統、大數據技術分享。 概述 隨著科技的進步,軟體系統的部署架構也在不斷演進,從以前傳統的物理機到虛擬機、Docker和Kubernetes,我們經歷了一系列變化。 這些技術的引入給我們帶來了更高的資源利用率、更快的部署速度和更強大的擴展性,下麵 ...
  • C++ 中要在一個函數內返回不同類型的值,你可以使用 C++17 引入的 std::variant 或 std::any,或者使用模板和多態。下麵將分別介紹這些方法。 方法一:使用 std::variant std::variant 允許你在一個函數內返回不同類型的值,但它要求所有可能的返回類型都在 ...
  • 主要探討了SpringMVC中的流程跳轉和不同形式的控制器之間的跳轉方式。首先回顧了JavaWeb中流程跳轉的核心代碼和頁面跳轉方式,並展示了在Web.xml中添加Servlet以及執行這些方式的示例。隨後,介紹了Spring MVC中的四種跳轉形式,包括控制器到JSP頁面的forward和redi... ...
  • 寫在前面 先吐槽兩句,搞個mysql安裝配置弄了4個小時,怎麼都是外網無法訪問,我靠,我特麽也是服了。 當然,後來我投降了,明天再說,學什麼不是學,娘的,換個方向,狀態依然在! Sijax是什麼? 代表 Simple Ajax ,它是一個 Python / jQuery 庫,使用 jQuery.aj ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...