主頁 > 後端開發 > Golang掃盲式學習——GO并發 | (一)

Golang掃盲式學習——GO并發 | (一)

2023-06-01 07:40:59 後端開發

并發與并行??

并發與并行的概念和區別

并行:同一個時間段內多個任務同時在不同的CPU核心上執行,強調同一時刻多個任務之間的”同時執行“,

并發:同一個時間段內多個任務都在進展,強調多個任務間的”交替執行“,

隨著硬體水平的提高,現在的終端主機都是多個CPU,每個CPU都是多核結構,當多個CPU同時運行起來,跑不同的任務,這屬于并行;在一個CPU里的多個核心里同時運行不同的任務,同樣也屬于并行,而并發是關注一個核心里的多個任務,這時需要交替執行,就是并發,

CPU是計算單元,有資料才能進行計算,當一個任務被網路I/O阻塞,CPU沒有資料,就會處于等待,顯然,若是能夠將等待的時間利用起來,資源利用率會提高,因此,并發處理的主要目的是提高CPU和資源的利用率,

Go語言并發與CPU核心的關系

  • Go并發是基于Goroutine和Channel實作的,

Goroutine是Go語言的并發執行單元,Channel用于Goroutine之間的通信與同步,

  • Goroutine的執行依賴于作業系統的執行緒調度,

Goroutine自身不具備執行背景關系,它必須依存在作業系統執行緒上才可以真正執行,當一個Goroutine被創建時,Go runtime會自動選擇一個空閑的作業系統執行緒,將這個Goroutine的執行背景關系系結到該執行緒上,

上圖中G表示Goroutine,P表示一個調度的背景關系(包含了運行 Goroutine 的資源),M表示一個OS執行緒,一個作業系統執行緒可以同時關聯多個Goroutine,這些Goroutine會被Go runtime高效地在該執行緒上調度執行,但任意時刻只會有一個Goroutine獲得執行緒的執行權進行運行(G0就是獲得執行緒權的Goroutine),當關聯的作業系統執行緒終止時,系結在該執行緒上的所有Goroutine也會被終止,

  • 在單核CPU上,即使有許多Goroutine,同一時刻也只能有一個Goroutine真正在CPU上運行,

單核CPU同一時刻只能執行一個執行緒,即使有許多Goroutine,也只有獲得CPU執行權的那個Goroutine在真正運行,其他Goroutine會被掛起,等待下次被調度執行,

  • 在多核CPU上,作業系統可以將不同的Goroutine直接調度到不同的CPU核心上運行,

這樣多個Goroutine就可以同時真正運行,實作并行執行,此時Go并發程式可以發揮多核CPU的強大計算能力,Go runtime會有智能的調度策略,將Goroutine均勻地分布在所有CPU核心上或者以負載均衡的方式進行調度,這取決于Goroutine的數量和系統的CPU核心數,

GMP模型如上圖所示,圖中涉及5個重要的物體,

  • 全域佇列(run queue):存放等待運行的Goroutine,
  • 本地佇列(local queue):和P連接的佇列,存放的也是等待運行的Goroutine,存放數量有限,一般不超過256個,新建Goroutine時,優先在本地佇列存放,然后再放置全域佇列,
  • P:被稱為處理器,包含了運行 Goroutine 的資源,一個Goroutine想要運行,必須先獲取P,P的數量是可配置的,最多有GOMAXPROCS個,
  • G:指代Goroutine,會被Go runtime智能化調度,當一個執行緒M空閑時,首先會從全域佇列里獲取Goroutine,若全域佇列為空,則會從周邊的執行緒”偷“一半Goroutine放到本地佇列,
  • M:執行緒由OS調度器分配到CPU的核上執行,當一個執行緒阻塞時,會導致和該執行緒的其它Goroutine”餓死“,此時,Go runtime會解綁P和M,將一個新的M分配給P避免”餓死“情況發生,

Goroutine與OS執行緒的區別

  • 生命周期不同

Goroutine由Go runtime 管理生命周期,創建和銷毀由runtime調度完成,OS執行緒是由作業系統內核來管理生命周期,

  • 調度不同

Goroutine由Go runtime的調度器進行調度,OS執行緒的調度是由作業系統內核根據時間片進行的,

  • 關聯關系不同

每個OS執行緒與一個Goroutine關聯,但一個Goroutine不一定對應一個OS執行緒,多個Goroutine可能對應同一個OS執行緒,Go runtime會動態地將Goroutine映射到執行緒上,

  • 資源消耗不同

創建和維護OS執行緒需要較多資源,而Goroutine的資源消耗很小,一個應用程式可以同時存在成千上萬個Goroutine,但OS執行緒數目通常較小,

  • 通信方式不同

Goroutine之間通信使用Channel,而OS執行緒通常使用共享記憶體來通信,

  • 并發數不同

一個Go程式的并發度可以達到上百萬,這是由于Goroutine的高效率實作,OS執行緒難以達到如此高的并發度,

Goroutine??

Goroutine的概念與特點

概念:Goroutine是一個輕量級的執行單元,用于執行并發任務,多個Goroutine可以在同一地址空間中執行,且Go runtime會管理其生命周期,Goroutine通過Channel進行通信,

特點:

  • 輕量級:創建和維護Goroutine的開銷很小,一個程式可以同時存在成千上萬個Goroutine,
  • 并發執行:Goroutine允許程式利用多核CPU的優勢進行并發執行,
  • 自動調度:Goroutine的調度完全由Go運行時進行管理,開發者不需要關心底層細節,
  • 資源共享:多個Goroutine可以訪問共享的記憶體資源,這使得Goroutine之間可以高效地通信與協作,
  • 無需回收:Goroutine不需要手動回收即可釋放資源,運行時會自動回收結束的Goroutine,
  • 動態擴展:一個Go程式可以從幾個Goroutine開始,然后動態地創建更多Goroutine來利用多核資源,
  • 高并發:利用Goroutine可以輕易地撰寫高并發程式,一個服務器程式可以同時接待成千上萬個客戶端,
  • Channel通信:Goroutine之間可以通過Channel進行高效的訊息通信與同步,這使得撰寫并發程式變得簡單,

創建Goroutine的語法

go func(){}():第一個func() {...} 定義了一個匿名函式(anonymous function),第二個()代表呼叫這個匿名函式,

package main

import (
	"fmt"
	"time"
)

func main() {
	// 創建一個Goroutine
	go func() {
		fmt.Println("Hello from Goroutine!")
	}()

	time.Sleep(1)
	// 主Goroutine
	fmt.Println("Hello from main!")
}

Goroutine的調度與背景關系切換

為了讓不同的Goroutine有機會運行,runtime會在Goroutine之間進行背景關系切換,當一個Goroutine運行一定時間或遇到channel操作時,會主動交出執行緒的執行權,這時runtime會從其他掛起的Goroutine中選擇一個繼續運行,背景關系切換涉及到保存當前運行Goroutine的程式計數器、堆疊指標等背景關系資訊,并恢復下一個要運行的Goroutine的背景關系資訊,這個程序需要一定的時間開銷,

Goroutine存在的記憶體問題及解決方案

存在的問題:

  • 堆疊溢位:每個Goroutine都有一個私有的堆疊,默認堆疊大小為2MB,如果函式呼叫太深會導致堆疊溢位,

  • 堆溢位:如果Goroutine中分配過多的堆物件,也會導致記憶體溢位,

  • 記憶體泄露:如果Goroutine退出時沒有釋放之前分配的記憶體,會導致這部分記憶體泄漏,

解決方案:

  • Goroutine堆疊大小

方法一:設定runtime.GOMAXPROCS(n, stackSize),其中,n指定要使用的P的個數,設定為0表示使用所有CPU核心,stackSize指定新的默認Goroutine堆疊大小,單位為位元組,

方法二:創建Goroutine時,指定堆疊的大小,go func(params) { /* ... */ }(stackSize, params),stackSize必須是第一個引數,在params之前指定,注意:stackSize的引數只在編譯時起作用,用于指定Goroutine的堆疊大小,在被呼叫的函式內部,它無法訪問這個引數,

  • 避免無限遞回

在Goroutine中呼叫無限遞回函式會引起堆疊溢位,應該避免這種情況發生,

  • 設定垃圾回收閾值

可以通過runtime.GOGC來設定垃圾回收器的閾值,觸發更頻繁的垃圾回收來避免堆溢位,例如runtime.GOGC=200 設定垃圾回收閾值為200,

  • 手動回收堆記憶體

當一個Goroutine結束時,其私有堆疊記憶體會被識訓,但堆記憶體不會自動回收,因此,需要在Goroutine結束前手動回收不再使用的堆記憶體,否在會發生記憶體泄露,

go func() {
    // 分配一些堆空間
    buf := make([]byte, 100)
    // 使用buf...
   
    // Goroutine結束前手動回收buf
    buf = nil 
}()
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()   // Goroutine結束 decrement WaitGroup計數
    // 分配記憶體并使用...
    buf := make([]byte, 100) 
    // 使用buf...
    buf = nil        // 手動回收記憶體
}()
wg.Wait()           // 等待Goroutine結束

Channel??

Channel的概念與特點

Channels are not closed by default. They need to be closed explicitly with the Close method to indicate that no more values will be sent on the channel.

Channel是一個通信機制,它可以使多個Goroutine之間相互發送資料,Channel允許任意兩個Goroutine通過它異步地傳遞資訊,

特點:

  • 方向性:Channel可以是雙向的(default)或單向的(指定方向時),單向Channel按發送/接收方向分為發送(chan<-)和接收(<-chan)Channel,

  • 型別安全:Channel在宣告時需指定元素型別,之后只能傳送該型別的元素,這保證了Channel通信的型別安全,

  • FIFO:Channel實作了先入先出的規則,發送的元素按順序被接收,

  • 阻塞:向一個滿的Channel發送資料會導致發送方阻塞,從一個空的Channel接收資料會導致接收方阻塞,

  • 緩沖:可以指定Channel的緩沖區大小,向一個未滿的緩沖Channel發送資料不會阻塞,

  • 關閉:關閉的Channel無法再發送資料,但可以繼續從中接收資料,向關閉的Channel發送資料會panic,

  • 無緩沖或滿的Channel導致Goroutine阻塞,這可用于實作同步和協作,

  • Channel支持for range形式的接收,這會不斷接收Channel的資料知道它被關閉,

  • Channel可用于函式間傳遞資料,實作異步執行的函式之間的資料通信,

無緩沖Channel與有緩沖Channel

  • 無緩沖Channel

unbuffered channel 就是緩沖大小為 0 的 channel,無緩沖區的 channel 本身是不存放資料的,在發送和接收都會被阻塞,也就是相當于,你現在是一個 send 身份,但是當另外一個沒有 receive 你發送的值之前,你一直處于阻塞(等待接收)狀態;就好比你遞東西給別人,別人沒接,你就要一直舉著東西,相反,如果你現在是一個 receive 身份,你就會一直阻塞(等待發送)狀態,在你拿到值之前,你會一直等待,就好比你準備要接東西,別人遲遲不給你,你就要一直等著,

package main

import (
	"fmt"
)

func main() {
	ch := make(chan struct{})
	go func() {
		defer close(ch)
		v := <-ch
		fmt.Printf("receive a struct: %v\n", v)
	}()

	ch <- struct{}{}
	fmt.Println("send a struct")
}
  • 有緩沖Channel

有緩沖的Channel就是設定了一個buffersize,作為緩沖區的大小,

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 1)  // 設定buffersize大小為1
	go func() {
		defer close(ch)
		defer fmt.Println("我關閉了")
		ch <- 1
		ch <- 2
		fmt.Println("send a struct")
	}()

	time.Sleep(5 * time.Second)
	for {
		v, ok := <-ch
		if !ok {
			fmt.Println("channel closed")
			break
		}
		fmt.Printf("received a struct %v\n", v)
	}
}
// output
received a struct 1
received a struct 2
send a struct
我關閉了
channel closed

創建Channel時,當buffersize==1時,相當于buffersize==0,表示創建一個無緩沖的通道,所以,當向通道傳入一個1時,緩沖區就滿了,此時,必須等接收端將1讀走,才能繼續向channel發送2,

將buffersize修改為2時,表示創建一個有緩沖的通道,發送者會一次性將1和2發送到通道里,然后就關閉Channel的寫入端,睡眠5秒后,就由接收端將結果讀出,

// output
send a struct
我關閉了
received a struct 1
received a struct 2
channel closed

應用:實作一個生產者消費者模型

package main

import (
	"fmt"
	"time"
)

func producer(out chan<- int) {
	for i := 0; i < 10; i++ {
		out <- i  // 生產一個資料,發送到Channel
		fmt.Println("生產者:", i)
	}
	close(out)
}

func consumer(in <-chan int) {
	for num := range in {  // 從Channel中接收資料
		fmt.Println("消費者:", num)  // 消費資料
	}
}

func main() {
	// 創建一個管道
	ch := make(chan int)

	go producer(ch)  // 啟動生產者Goroutine
	go consumer(ch)  // 啟動消費者Goroutine
	go consumer(ch)  // 啟動消費者Goroutine

	time.Sleep(5 * time.Second)  // 等待
}

既然都看到這里了,不如順手點個推薦吧!

參考資料:

https://morsmachine.dk/go-scheduler

https://go.dev/blog/pipelines

https://www.kelche.co/blog/go/golang-scheduling/

https://www.kelche.co/blog/go/channels/

https://juejin.cn/post/7231887884739346489

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553947.html

標籤:其他

上一篇:StampedLock:高并發場景下一種比讀寫鎖更快的鎖

下一篇:返回列表

標籤雲
其他(160072) Python(38193) JavaScript(25469) Java(18172) C(15235) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7219) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5344) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4580) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1979) 功能(1967) Web開發(1951) HtmlCss(1950) C++(1928) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1879) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Golang掃盲式學習——GO并發 | (一)

    ## 并發與并行😣 ### 并發與并行的概念和區別 并行:同一個時間段內多個任務同時在不同的CPU核心上執行。強調同一時刻多個任務之間的”**同時執行**“。 并發:同一個時間段內多個任務都在進展。強調多個任務間的”**交替執行**“。 ![](https://img2023.cnblogs.co ......

    uj5u.com 2023-06-01 07:40:59 more
  • StampedLock:高并發場景下一種比讀寫鎖更快的鎖

    摘要:在讀多寫少的環境中,有沒有一種比ReadWriteLock更快的鎖呢?有,那就是JDK1.8中新增的StampedLock! 本文分享自華為云社區《【高并發】高并發場景下一種比讀寫鎖更快的鎖》,作者: 冰 河。 什么是StampedLock? ReadWriteLock鎖允許多個執行緒同時讀取共 ......

    uj5u.com 2023-06-01 07:40:46 more
  • BIO、NIO、AIO區別詳解

    ###BIO:同步阻塞 主執行緒發起io請求后,需要等待當前io操作完成,才能繼續執行。 ###NIO:同步非阻塞 引入selector、channel、等概念,當主執行緒發起io請求后,輪詢的查看系統是否準備好執行io操作,沒有準備好則主執行緒不會阻塞會繼續執行,準備好主執行緒會阻塞等待io操作完成。 # ......

    uj5u.com 2023-06-01 07:40:40 more
  • Python連接es筆記四之創建和洗掉操作

    > 本文首發于公眾號:Hunter后端 > 原文鏈接:[Python連接es筆記四之創建和洗掉操作](https://mp.weixin.qq.com/s/ZCe0JT9TDEiZI7M5dxC9qA) 這一篇筆記介紹一下索引和資料的創建和洗掉。 其實對于索引來說,如果可以接觸到 kibana 的話 ......

    uj5u.com 2023-06-01 07:40:01 more
  • Python潮流周刊#4:Python 2023 語言峰會

    你好,我是貓哥。這里記錄每周值得分享的 Python 及通用技術內容,本期是特別加更版,聚焦于 Python 官方 2023 年語言峰會的系列博客。 博客原文:https://pythoncat.top/posts/2023-05-31-weekly4 每年在 PyCon US 開始之前,Pytho ......

    uj5u.com 2023-06-01 07:39:47 more
  • 基于 Web 實作 m3u8 視頻播放的簡單應用示例

    # 基于 Web 實作 m3u8 視頻播放的簡單應用示例 ## 實作思路 將視頻(MP4 等)轉換為 M3U8 視頻的服務,可以按照以下步驟進行操作: 1. 將視頻(MP4 等)轉換為 M3U8:在服務中,使用適當的工具(如 FFmpeg)將接收到的視頻(MP4 等)轉換為 M3U8 格式。這將生成 ......

    uj5u.com 2023-06-01 07:39:37 more
  • < Python全景系列-9 > Python 裝飾器:優雅地增強你的函式和類

    裝飾器在 Python 中扮演了重要的角色,這是一種精巧的語言特性,讓我們能夠修改或增強函式和類的行為,無需修改它們的源代碼。這篇文章將深入探討裝飾器的所有相關主題,包括裝飾器的基礎知識、實作與使用、作業原理,以及通過實際例子學習裝飾器的獨特用法。 ......

    uj5u.com 2023-06-01 07:39:33 more
  • Java中序列化和反序列化解釋

    在Java中,序列化(Serialization)是指將物件的狀態轉換為位元組流的程序,以便將其保存到檔案、在網路中傳輸或持久化到資料庫中。而反序列化(Deserialization)則是將位元組流轉換回物件的程序,恢復物件的狀態。 序列化和反序列化主要用于以下場景: 1. 物件持久化:通過序列化,可以 ......

    uj5u.com 2023-06-01 07:39:08 more
  • 吃透Redis面試八股文

    > 內容摘自我的學習網站:topjavaer.cn Redis連環40問,絕對夠全! ## Redis是什么? Redis(`Remote Dictionary Server`)是一個使用 C 語言撰寫的,高性能非關系型的鍵值對資料庫。與傳統資料庫不同的是,Redis 的資料是存在記憶體中的,所以讀寫 ......

    uj5u.com 2023-06-01 07:38:56 more
  • 《面試1v1》synchronized

    > 原始碼都背下來了,你給我看這 **我是 javapub,一名 `Markdown` 程式員從👨?💻,八股文種子選手。** **面試官: 你好,我看到你的簡歷上寫著你熟悉 Java 中的 "synchronized" 關鍵字。你能給我講講它的作用嗎?** **候選人:** 當然,"synchro ......

    uj5u.com 2023-06-01 07:38:47 more