併發與並行,同步和非同步,Go lang1.18入門精煉教程,由白丁入鴻儒,Go lang併發編程之GoroutineEP13

来源:https://www.cnblogs.com/v3ucn/archive/2022/08/30/16640023.html
-Advertisement-
Play Games

如果說Go lang是靜態語言中的皇冠,那麼,Goroutine就是併發編程方式中的鑽石。Goroutine是Go語言設計體系中最核心的精華,它非常輕量,一個 Goroutine 只占幾 KB,並且這幾 KB 就足夠 Goroutine 運行完,這就能在有限的記憶體空間內支持大量 Goroutine協 ...


如果說Go lang是靜態語言中的皇冠,那麼,Goroutine就是併發編程方式中的鑽石。Goroutine是Go語言設計體系中最核心的精華,它非常輕量,一個 Goroutine 只占幾 KB,並且這幾 KB 就足夠 Goroutine 運行完,這就能在有限的記憶體空間內支持大量 Goroutine協程任務,方寸之間,運籌帷幄,用極少的成本獲取最高的效率,支持了更多的併發,毫無疑問,Goroutine是比Python的協程原理事件迴圈更高級的併發非同步編程方式。

GMP調度模型(Goroutine-Machine-Processor)

為什麼Goroutine比Python的事件迴圈高級?是因為Go lang的調度模型GMP可以參與系統內核線程中的調度,這裡G為Goroutine,是被調度的最小單元;M是系統起了多少個線程;P為Processor,也就是CPU處理器,調度器的核心處理器,通常表示執行上下文,用於匹配 M 和 G 。P 的數量不能超過 GOMAXPROCS 配置數量,這個參數的預設值為當前電腦的總核心數,通常一個 P 可以與多個 M 對應,但同一時刻,這個 P 只能和其中一個 M 發生綁定關係;M 被創建之後需要自行在 P 的 free list 中找到 P 進行綁定,沒有綁定 P 的 M,會進入阻塞狀態,每一個P最多關聯256個G。

說白了,就是GMP和Python一樣,也是維護一個任務隊列,只不過這個任務隊列是通過Goroutine來調度,怎麼調度?通過Goroutine和系統線程M的協商,尋找非阻塞的通道,進入P的本地小隊列,然後交給系統內的CPU執行,藉此,充分利用了CPU的多核資源。

而Python的協程方式僅僅停留在用戶態,它沒法參與到線程內核的調度,彌補方式是單線程多協程任務下開多進程,Go lang則是全權交給Goroutine,用戶不需要參與底層操作,同時又可以利用CPU的多核資源。

啟動Goroutine

首先預設情況下,golang程式還是由上自下的串列方式:

package main  
  
import (  
	"fmt"  
)  
  
func job() {  
	fmt.Println("任務執行")  
}  
func main() {  
	job()  
	fmt.Println("任務執行完了")  
}

程式返回:

任務執行  
任務執行完了

這裡job中的列印函數是先於main中的列印函數。

現在,在執行job函數前面加上關鍵字go,也就是啟動一個goroutine去執行job這個函數:

package main  
  
import (  
	"fmt"  
	"time"  
)  
  
func job() {  
	fmt.Println("任務執行")  
}  
func main() {  
	go job()  
	fmt.Println("任務執行完了")  
	time.Sleep(time.Second)  
}

註意,開啟Goroutine是在函數執行的時候開啟,並非聲明的時候,程式返回:



任務執行完了  
任務執行


可以看到,執行順序顛倒了過來,首先為什麼會先列印任務執行完了,是因為系統在創建新的Goroutine的時候需要耗費一些資源,因為就算只有幾kb,也需要時間來創建,而此時main函數所在的goroutine是繼續執行的。

第二,為什麼要人為的把main函數延遲一秒鐘?

因為當main()函數返回的時候main所在的Goroutine就結束了,所有在main()函數中啟動的goroutine會一同結束,所以這裡必須人為的“阻塞”一下main函數,讓它後於job結束,有點像公園如果要關門必須等最後一個游客走了才能關,否則就把游客關在公園裡了,出不去了。

與此同時,此邏輯和Python中的線程阻塞邏輯非常一致,用過Python多線程的朋友肯定知道要想讓所有子線程都執行完畢,必須阻塞主線程,不能讓主線程提前執行完,這和Goroutine有異曲同工之妙。

在Go lang中實現併發編程就是如此輕鬆,我們還可以啟動多個Goroutine:



package main  
  
import (  
	"fmt"  
	"sync"  
)  
  
var wg sync.WaitGroup  
  
func job(i int) {  
	defer wg.Done() // 協程結束就通知  
	fmt.Println("協程任務執行", i)  
}  
func main() {  
  
	for i := 0; i < 10; i++ {  
		wg.Add(1) // 啟動協程任務後入隊  
		go job(i)  
	}  
	wg.Wait() // 等待所有登記的goroutine都結束  
  
	fmt.Println("所有任務執行完畢")  
}  



程式返回:



協程任務執行 8  
協程任務執行 9  
協程任務執行 5  
協程任務執行 0  
協程任務執行 1  
協程任務執行 4  
協程任務執行 7  
協程任務執行 2  
協程任務執行 3  
協程任務執行 6  
所有任務執行完畢


這裡我們摒棄了相對土鱉的time.Sleep(time.Second)方式,而是採用sync包的WaitGroup方式,原理是當啟動協程任務後,在WaitGroup登記,當每個協程任務執行完成後,通知WaitGroup,直到所有的協程任務都執行完畢,然後再執行main函數所在的協程,所以“所有任務執行完畢”會在所有協程任務執行完畢後再列印。

和Python協程區別

我們再來看看,如果是Python,會怎麼做?



import asyncio  
import random  
  
async def job(i):  
  
    print("協程任務執行{}".format(i))  
    await asyncio.sleep(random.randint(1,5))  
    print("協程任務結束{}".format(i))  
  
  
  
async def main():  
  
    tasks = [asyncio.create_task(job(i)) for i in range(10)]  
      
    res = await asyncio.gather(*tasks)  
  
  
if __name__ == '__main__':  
    asyncio.run(main())


程式返回:

協程任務執行0  
協程任務執行1  
協程任務執行2  
協程任務執行3  
協程任務執行4  
協程任務執行5  
協程任務執行6  
協程任務執行7  
協程任務執行8  
協程任務執行9  
協程任務結束0  
協程任務結束1  
協程任務結束3  
協程任務結束6  
協程任務結束9  
協程任務結束8  
協程任務結束2  
協程任務結束4  
協程任務結束5  
協程任務結束7

可以看到,Python協程工作的前提是,必須在同一個事件迴圈中,同時邏輯內必須由用戶來手動切換,才能達到“併發”的工作方式,假設,如果我們不手動切換呢?

import asyncio  
import random  
  
async def job(i):  
  
    print("協程任務執行{}".format(i))  
    print("協程任務結束{}".format(i))  
  
  
  
async def main():  
  
    tasks = [asyncio.create_task(job(i)) for i in range(10)]  
      
    res = await asyncio.gather(*tasks)  
  
  
if __name__ == '__main__':  
    asyncio.run(main())

程式返回:

協程任務執行0  
協程任務結束0  
協程任務執行1  
協程任務結束1  
協程任務執行2  
協程任務結束2  
協程任務執行3  
協程任務結束3  
協程任務執行4  
協程任務結束4  
協程任務執行5  
協程任務結束5  
協程任務執行6  
協程任務結束6  
協程任務執行7  
協程任務結束7  
協程任務執行8  
協程任務結束8  
協程任務執行9  
協程任務結束9

一望而知,只要你不手動切任務,它就立刻回到了“串列”的工作方式,同步的執行任務,那麼協程的意義在哪兒呢?

所以,歸根結底,Goroutine除了可以極大的利用系統多核資源,它還能幫助開發者來切換協程任務,簡化開發者的工作,說白了就是,不懂協程工作原理,也能照貓畫虎寫go lang代碼,但如果不懂協程工作原理的前提下,寫Python協程併發邏輯呢?恐怕夠嗆吧。

結語

綜上,Goroutine的工作方式,就是多個協程在多個線程上切換,既可以用到多核,又可以減少切換開銷。但有光就有影,有利就有弊,Goroutine確實不需要開發者過度參與,但這樣開發者就少了很多自由度,一些定製化場景下,就只能採用單一的Goroutine手段,比如一些純IO密集型任務場景,像爬蟲,你有多少cpu的意義並不大,因為cpu老是等著你的io操作,所以Python這種協程工作方式在純IO密集型任務場景下並不遜色於Goroutine。


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

-Advertisement-
Play Games
更多相關文章
  • 1.數字類型 數字類型的數據可以相互的進行+-/*、也可以進行相互的比較(<>=) 1.1整型int age = 18 記錄年齡等整數 print(type(age))# int類型 int()方法可以將其他類型的數據轉換成int類型 1.1.2二、八、十六進位的相互轉換 1.十進位《 》二進位 # ...
  • 《笨辦法學Python3 》PDF高清版免費下載地址 ↑ ↑ ↑ ↑ ↑ ↑ ↑ 點擊即可下載 內容簡介 · · · · · · 本書是一本Python入門書籍,適合對電腦瞭解不多,沒有學過編程,但對編程感興趣的讀者學習使用。這本書以習題的方式引導讀者一步一步學習編程,從簡單的列印一直講到完整項目 ...
  • Java泛型02 5.自定義泛型 5.1自定義泛型類 基本語法: class 類名<T,R...>{//…表示可以有多個泛型 成員 } 註意細節: 普通成員可以使用泛型(屬性、方法) 使用泛型的數組不能初始化 靜態方法中不能使用類的泛型 泛型類的類型,是在創建類的對象時確定的(因為創建對象時,需要指 ...
  • ##HttpServletRequest request(請求) 所有的 和請求相關的操作,都用這對象來處理 當有請求來的時候 , request就被實例化 ##HttpServletResponse response(響應) 所有和響應相關的操作,都用這個對象來處理 當有請求來的時候 , resp ...
  • 問題描述: 前端使用Get請求並且使用請求體傳遞參數,後端使用@RequestBody註解封裝參數,這時會出現400的異常信息。 解決方法: 1、Get請求不要使用請求體,使用請求體的話用POST請求。(建議,這樣才是正常的規範寫法) 2、保留Get請求與請求體,後端也可以用對象來封裝請求體中的參數 ...
  • 首先上結構 mynode -> app5 -> urls.py & views.py | -> templates -> 5 -> upload.html | -> mynode -> urls.py | -> media 按照順序,先上app5/urls.py from django.urls i ...
  • 眾所周知,Go lang的作用域相對嚴格,數據之間的通信往往要依靠參數的傳遞,但如果想在多個協程任務中間做數據通信,就需要通道(channel)的參與,我們可以把數據封裝成一個對象,然後把這個對象的指針傳入某個通道變數中,另外一個協程從這個通道中讀出變數的指針,並處理其指向的記憶體對象。 通道的聲明與 ...
  • 來源:https://segmentfault.com/a/1190000021109130 問題描述 前幾天在幫同事排查生產一個線上偶發的線程池錯誤 邏輯很簡單,線程池執行了一個帶結果的非同步任務。但是最近有偶發的報錯: java.util.concurrent.RejectedExecutionE ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...