go-GMP

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

public class RandomNickName { public enum Gender{ MAN, WOMAN, UNKNOWN, ; } public static void main(String[] args) { String nickName = nickName(Gender. ...


go的協程和線程都繞不過GMP,關於GMP基本的工作流程,有go開發經驗的大致都懂,這邊更多關註GMP如何解決一些類似 協程饑渴的問題,以及底層的大致實現原理。

多線程迴圈

上篇講了單線程是如何迴圈的,這裡還是為 GMP的出場 大致介紹下。

工作模型

多個M都去全局G的隊列中獲取 g,所以,全局g的隊列需要上鎖。

改進版,增加本地隊列

這樣每個m都緩存了一個本地隊列,避免每次都去全局隊列裡面拿,而且一次也能拿多個,降低了全局隊列鎖獲取的開銷。

這個其實就是 GMP了。P指的就是為M管理要執行的g

P 的定義

在runtime2.go中有定義:

 刪除了很多源碼,只留下和這裡講的相關的代碼:
  type p struct {
    	id          int32 
        // M 線程
    	m           muintptr   // back-link to associated m (nil if idle)   
        // 可用的g的隊列,進入不用加鎖  嘚瑟
    	// Queue of runnable goroutines. Accessed without lock.
    	runqhead uint32 // 頭尾
    	runqtail uint32
    	runq     [256]guintptr // 256 的容量 
    	runnext guintptr // 下一個可執行的g
    }

整體結構如下:

完整的GMP模型就如下:

和上面的改進版很像,只是抽象出來一個P,專門來管理

p的作用

1. M與G之間的中介

2. P持有一些G,使得每次獲取G的時候不用從全局找

3. 大大減少了併發衝突的情況

代碼的邏輯

要回到 schedule(),上篇中有提到:
schedule()中有為 M查找,要運行的g,通過這個方法:

 gp, inheritTime, tryWakeP := findRunnable() // blocks until work is available

 // Finds a runnable goroutine to execute.
// Tries to steal from other P's, get g from local or global queue, poll network.
// 查詢一個 能運行的g去執行,嘗試去別的p裡面偷,或者從 本地、全局隊列了獲取。
//  poll network 涉及到了 epoll 和 poll 和這裡沒關係
func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
    // local runq 本地拿 
	if gp, inheritTime := runqget(pp); gp != nil {
		return gp, inheritTime, false
	}
	// global runq 全局拿 
	if sched.runqsize != 0 {
		lock(&sched.lock) // 全局隊列拿 需要上鎖
		gp := globrunqget(pp, 0)
		unlock(&sched.lock)
        }
        // 如果還拿不到 就去偷 去別的p裡面偷	
        gp, inheritTime, tnow, w, newWork := stealWork(now)
}

func runqget(pp *p) (gp *g, inheritTime bool) {
	// If there's a runnext, it's the next G to run.
	next := pp.runnext //從p的next獲取的下一個

	if next != 0 && pp.runnext.cas(next, 0) {
		return next.ptr(), true
	}
}
源碼都是未截取完整的。

看下全局怎麼拿的:

//Try get a batch of G's from the global runnable queue.
// sched.lock must be held.
func globrunqget(pp *p, max int32) *g {
	assertLockHeld(&sched.lock)
	n := sched.runqsize/gomaxprocs + 1
	if n > sched.runqsize {
		n = sched.runqsize
	}
	if max > 0 && n > max {
		n = max
	}
	if n > int32(len(pp.runq))/2 {
		n = int32(len(pp.runq)) / 2
	}
	sched.runqsize -= n

	gp := sched.runq.pop()
	n--
	for ; n > 0; n-- {
		gp1 := sched.runq.pop()
		runqput(pp, gp1, false)
	}
	return gp
}

經過了一系列的計算,最終確定拿 n個g放回本地。 因為傳過來的 max=0
所以 n 為 len(pp.runq)/ 2sched.runqsize/gomaxprocs + 1 中的數量大的那個。
runq [256]guintptr 聲明為256
本地隊列容量的一半 128,或者 全局隊列的個數除以 gomaxprocs(p的個數) + 1,最多拿128個。

steal

上面邏輯講了,如果全局也拿不到,就調用 stealWork 去別的p裡面拿, 看下代碼:

// stealWork attempts to steal a runnable goroutine or timer from any P.
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
  // 具體的邏輯,就不看了。就是遍歷裡面的p 從符合條件的p的本地隊列,拿一半過去。
 }

小結:

如果在本地或者全局隊列中都找不到G, 去別的P中“偷〞,增強了線程的利用率

協程新建

代碼在 proc.go中

func newproc(fn *funcval) {
	gp := getg() 
	pc := getcallerpc()
	systemstack(func() {
       // 新創建一個 g
		newg := newproc1(fn, gp, pc)

		pp := getg().m.p.ptr() // 獲取當前g 的 p 
		runqput(pp, newg, true) // 通過這函數,將g放置
	})
}

// getg returns the pointer to the current g.
func getg() *g

小結:

1. 優先獲取當前的P 

(這點上有看到說是隨機獲取一個p,但是從 getg() 官方描述來看,是獲取的當前p,如果這裡我未理清楚,可以評論下)

2. 將新協程放入P的runnext(插隊)
3. 若P本地隊列滿,放入全局隊列

問題:

目前為止,已經解決了第一個問題

多線程併發時,會搶奪協程隊列的全局鎖

但是 協程順序執行,無法併發 ,如一個協程執行時間非常長,一直占著m,就會導致其他g無法及時響應。


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

-Advertisement-
Play Games
更多相關文章
  • 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 ...
  • Sun公司提供了JavaMail用來實現郵件發送,但是配置煩瑣,Spring中提供了JavaMailSender用來簡化郵件配置,Spring Boot則提供了MailSenderAutoConfiguration對郵件的發送做了進一步簡化。 v準備工作 開通POP3/SMTP服務或者IMAP/SM ...
  • C語言分支結構詳解 1. if 語句 在本篇博客文章中,我們將深入探討C語言中的if語句及其相關用法。if語句是一種用於條件判斷的分支語句,它允許我們根據條件的真假來執行不同的代碼塊。 1.1 if 語句的基本語法和用法 if語句的基本語法如下所示: if (條件) { // 條件為真時執行的代碼塊 ...
  • CF786 我不會告訴你鏈接在圖片里 CF786A CF786A題意 給出一個大小為 \(n\) 的環,點順時針從 \(1\to n\) 編號,兩個人(設為 \(0,1\))輪流移動其中的一個棋子。 對於第 \(opt\) 人,他能夠將這個棋子順時針移動 \(x\in S_{opt}\)(\(S_{ ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...