Go 并發(協程,通道,鎖,協程控制)

一.協程(Goroutine)

并發:指程序能夠同時執行多個任務的能力,多線程程序在一個核的cpu上運行,就是并發。

并行:多線程程序在多個核的cpu上運行,就是并行。

并發主要由切換時間片來實現"同時"運行,并行則是直接利用多核實現多線程的運行,go可以設置使用核數,以發揮多核計算機的能力。

協程:

  • ?Go 中的并發執行單位,類似于輕量級的線程。

  • Goroutine 的調度由 Go 運行時管理,用戶無需手動分配線程。

  • 使用 go 關鍵字啟動 Goroutine。

  • Goroutine 是非阻塞的,可以高效地運行成千上萬個 Goroutine。

  • 在main()函數調用時就開啟了一個主協程,主協程停止則其他的由主協程創建的分協程也停止

  • OS線程(操作系統線程)具有棧內存(通常為2MB),一個goroutine的棧在其生命周期開始時只有很小的棧(典型情況下2KB),goroutine的棧不是固定的,他可以按需增大和縮小,goroutine的棧大小限制可以達到1GB

協程的好處:

  • 輕量級:協程的創建和切換開銷非常小,可以在一個程序中創建大量協程。
  • 自動調度:協程由 Go 運行時自動調度,開發者不需要手動管理線程的創建和銷毀。
  • 共享內存:協程之間可以共享相同的地址空間,簡化了內存管理和數據共享。
  • 非阻塞:協程之間的通信和同步是非阻塞的,避免了傳統線程中的鎖競爭問題。

協程與線程的區別:

協程:

  1. 非操作系統提供而是由用戶自行創建和控制的用戶態‘線程’,比線程更輕量級。
  2. 在一個Go程序中同時創建成百上千個goroutine是非常普遍的,一個goroutine會以一個很小的棧開始其生命周期,一般只需要2KB

線程:

  1. 由操作系統管理,創建和切換開銷較大,適用于需要高性能和復雜調度的場景。
  2. 線程是進程的一個執行實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。

問題:

通常主協程運行的速度大于創建的分協程,導致程序運行完了,分協程還沒運行就結束了

解決方法:

? ? ? ?1.通過time包中sleep函數讓主協程睡眠一段時間,但是睡眠時間是固定的從而分協程已經運行完但是主協程還在睡眠。

? ? ? ?2.通常在main goroutine 中使用sync.WaitGroup來等待 其他goroutine 完成后再退出。

package mainimport ("fmt""sync"
)// 聲明全局等待組變量
var wg sync.WaitGroupfunc hello() {fmt.Println("hello")wg.Done() // 告知當前goroutine完成
}func main() {wg.Add(1) // 登記1個goroutinego hello()fmt.Println("你好")wg.Wait() // 阻塞等待登記的goroutine完成
}

? ?協程的調度(GMP):

  • G:表示 goroutine,每執行一次go f()就創建一個 G,包含要執行的函數和上下文信息。

  • 全局隊列(Global Queue):存放等待運行的 G。

  • P:表示 goroutine 執行所需的資源,最多有 GOMAXPROCS 個。

  • P 的本地隊列:同全局隊列類似,存放的也是等待運行的G,存的數量有限,不超過256個。新建 G 時,G 優先加入到 P 的本地隊列,如果本地隊列滿了會批量移動部分 G 到全局隊列。

  • M:線程想運行任務就得獲取 P,從 P 的本地隊列獲取 G,當 P 的本地隊列為空時,M 也會嘗試從全局隊列或其他 P 的本地隊列獲取 G。M 運行 G,G 執行之后,M 會從 P 獲取下一個 G,不斷重復下去。

  • Goroutine 調度器和操作系統調度器是通過 M 結合起來的,每個 M 都代表了1個內核線程,操作系統調度器負責把內核線程分配到 CPU 的核上執行。

  • GOMAXPROCS :Go運行時的調度器使用GOMAXPROCS參數來確定需要使用多少個 OS 線程來同時執行 Go 代碼。默認值是機器上的 CPU 核心數。例如在一個 8 核心的機器上,GOMAXPROCS 默認為 8。Go語言中可以通過runtime.GOMAXPROCS函數設置當前程序并發時占用的 CPU邏輯核心數。(Go1.5版本之前,默認使用的是單核心執行。Go1.5 版本之后,默認使用全部的CPU 邏輯核心數。)

調度器步驟

  1. 創建與入隊:G(協程)創建后,優先入 P 本地隊列,若本地隊列滿,會放到全局隊列。
  2. 調度準備:P 綁定 M(內核線程映射),M 從 P 本地隊列取 G 執行;本地隊列空時,會從全局隊列或其他 P 偷取 G 補充。
  3. 執行與切換:M 執行 G,若 G 阻塞(如 I/O 操作),P 會解綁當前 M、關聯新 M 繼續調度其他 G;G 恢復后重新入隊等待執行 。
  4. 資源協調:GOMAXPROCS 控制 P 數量,協調 CPU 核心(通過操作系統調度器)與 M 映射,讓 G 高效利用計算資源,實現并發調度 。

二.通道(channel)

問題:

單純地將函數并發執行是沒有意義的。函數與函數間需要交換數據才能體現并發執行函數的意義。雖然可以使用共享內存(通過調用函數)進行數據交換,但是共享內存在不同的 goroutine 中容易發生競態問題。為了保證數據交換的正確性,很多并發模型中必須使用互斥量對內存進行加鎖,這種做法勢必造成性能問題。

channel:

單純地將函數并發執行是沒有意義的。函數與函數間需要交換數據才能體現并發執行函數的意義。從而引出通道實現協程之前的數據傳輸與共享

channel特點:

  1. Go 語言中的通道(channel)是一種特殊的類型。通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規則,保證收發數據的順序
  2. 每一個通道都是一個具體類型的導管,也就是聲明channel的時候需要為其指定元素類型。
  3. 支持同步和數據共享,避免了顯式的鎖機制。
  4. 使用?chan?關鍵字創建,通過?<-?操作符發送和接收數據。
  5. 聲明的通道類型變量需要使用內置的make函數初始化之后才能使用
make(chan 元素類型, [緩沖大小])//默認值為nil
var ch chan int
fmt.Println(ch) // <nil>ch4 := make(chan int)
ch5 := make(chan bool, 1)  // 聲明一個緩沖區大小為1的通道x := <- ch // 從ch中接收值并賦值給變量x
<-ch       // 從ch中接收值,忽略結果<- chan int // 只接收通道,只能接收不能發送
chan <- int // 只發送通道,只能發送不能接收

緩存通道和無緩存通道的區別:

無緩存通道:無緩沖的通道只有在有接收方能夠接收值的時候才能發送成功,否則會一直處于等待發送的階段。同理,如果對一個無緩沖通道執行接收操作時,沒有任何向通道中發送值的操作那么也會導致接收操作阻塞

緩存通道:只要通道的容量大于零,那么該通道就屬于有緩沖的通道,通道的容量表示通道中最大能存放的元素數量。當通道內已有元素數達到最大容量后,再向通道執行發送操作就會阻塞,除非有從通道執行接收操作。

注意事項:

  • 對一個關閉的通道再發送值就會導致 panic。
  • 對一個關閉的通道進行接收會一直獲取值直到通道為空。
  • 對一個關閉的并且沒有值的通道執行接收操作會得到對應類型的零值。
  • 關閉一個已經關閉的通道會導致 panic。

三.鎖

當多個 goroutine 同時操作一個資源(臨界區)或者一個全局變量資源時的情況,這種情況下就會發生競態問題(數據競態)如多個協程同時對map進行存和刪除

互斥鎖:
互斥鎖是一種常用的控制共享資源訪問的方法,它能夠保證同一時間只有一個 goroutine 可以訪問共享資源。Go 語言中使用sync包中提供的Mutex類型來實現互斥鎖。

sync.Mutex提供了兩個方法供我們使用。
func (m *Mutex) Lock()?? ?獲取互斥鎖
func (m *Mutex) Unlock()?? ?釋放互斥鎖

讀寫互斥鎖
互斥鎖是完全互斥的,但是實際上有很多場景是讀多寫少的,當我們并發的去讀取一個資源而不涉及資源修改的時候是沒有必要加互斥鎖的,這種場景下使用讀寫鎖是更好的一種選擇。讀寫鎖在 Go 語言中使用sync包中的RWMutex類型。

func (rw *RWMutex) Lock()?? ?獲取寫鎖
func (rw *RWMutex) Unlock()?? ?釋放寫鎖
func (rw *RWMutex) RLock()?? ?獲取讀鎖
func (rw *RWMutex) RUnlock()?? ?釋放讀鎖
func (rw *RWMutex) RLocker() Locker?? ?返回一個實現Locker接口的讀寫鎖

讀寫鎖分為兩種:讀鎖和寫鎖。當一個 goroutine 獲取到讀鎖之后,其他的 goroutine 如果是獲取讀鎖會繼續獲得鎖,如果是獲取寫鎖就會等待;而當一個 goroutine 獲取寫鎖之后,其他的 goroutine 無論是獲取讀鎖還是寫鎖都會等待。

四.協程控制

1.通過多返回值,判斷通道是否已經關閉

value, ok := <- ch

value:從通道中取出的值,如果通道被關閉則返回對應類型的零值。
ok:通道ch關閉時返回 false,否則返回 true。

2.for-range來循環遍歷接收通道的值,當通道的值為空時,結束循環

func f3(ch chan int) {
for v := range ch {
fmt.Println(v)
}
}

通常我們會選擇使用for range循環從通道中接收值,當通道被關閉后,會在通道內的所有值被接收完畢后會自動退出循環。

注意:ch通道在遍歷到最后的數據的時候,如果通道沒有關閉,導致 range 一直等待新的數據,而發送方已經發送完所有數據。所以發送方發送完數據必須關閉才能用range。

3.select多路復用

在某些場景下我們可能需要同時從多個通道接收數據。通道在接收數據時,如果沒有數據可以被接收那么當前 goroutine 將會發生阻塞。你也許會寫出如下代碼嘗試使用遍歷的方式來實現從多個通道中接收值。

for{
// 嘗試從ch1接收值
data, ok := <-ch1
// 嘗試從ch2接收值
data, ok := <-ch2

}
這種方式雖然可以實現從多個通道接收值的需求,但是程序的運行性能會差很多。Go 語言內置了select關鍵字,使用它可以同時響應多個通道的操作。

Select 的使用方式類似于之前學到的 switch 語句,它也有一系列 case 分支和一個默認的分支。每個 case 分支會對應一個通道的通信(接收或發送)過程。select 會一直等待,直到其中的某個 case 的通信操作完成時,就會執行該 case 分支對應的語句。具體格式如下:

select {
case <-ch1:
//...
case data := <-ch2:
//...
case ch3 <- 10:
//...
default:
//默認操作
}

Select 語句具有以下特點:

  • 可處理一個或多個 channel 的發送/接收操作。
  • 如果多個 case 同時滿足,select 會隨機選擇一個執行。
  • 對于沒有 case 的 select 會一直阻塞,可用于阻塞 main 函數,防止退出。
  • 如果一個case中有多個通道發送數據,則接收時會串行排列等待被接受

4.通過一個新的通道數據來控制主協程等待分協程執行完在執行

cah1 := make(chan int, 100)cha2 := make(chan bool)go s(cah1, cha2)for k := 0; k < 100; k++ {cah1 <- k*2 - 1}close(cah1)<-cha2
}// 控制主協程等待分協程運行,可以用通道信號,當分協程中要執行的內容執行完
// 在向cha2發送信號,在主協程等待信號,這樣的話就可以讓主協程等待
func s(cah1 chan int, cha2 chan bool) {for v := range cah1 {fmt.Println(v)}cha2 <- false
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92563.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92563.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92563.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

圖機器學習(15)——鏈接預測在社交網絡分析中的應用

圖機器學習&#xff08;15&#xff09;——鏈接預測在社交網絡分析中的應用0. 鏈接預測1. 數據處理2. 基于 node2vec 的鏈路預測3. 基于 GraphSAGE 的鏈接預測3.1 無特征方法3.2 引入節點特征4. 用于鏈接預測的手工特征5. 結果對比0. 鏈接預測 如今&#xff0c;社交媒體已成為…

每日一算:華為-批薩分配問題

題目描述"吃貨"和"饞嘴"兩人到披薩店點了一份鐵盤&#xff08;圓形&#xff09;披薩&#xff0c;并囑咐店員將披薩按放射狀切成大小相同的偶數個小塊。但是粗心的服務員將披薩切成了每塊大小都完全不同的奇數塊&#xff0c;且肉眼能分辨出大小。由于兩人都…

Transfusion,Show-o and Show-o2論文解讀

目錄 一、Transfusion 1、概述 2、方法 二、Show-o 1、概述 2、方法 3、訓練 三、Show-o2 1、概述 2、模型架構 3、訓練方法 4、實驗 一、Transfusion 1、概述 Transfusion模型應該是Show系列&#xff0c;Emu系列的前傳&#xff0c;首次將文本和圖像生成統一到單…

聊聊 Flutter 在 iOS 真機 Debug 運行出現 Timed out *** to update 的問題

最近剛好有人在問&#xff0c;他的 Flutter 項目在升級之后出現 Error starting debug session in Xcode: Timed out waiting for CONFIGURATION_BUILD_DIR to update 問題&#xff0c;也就是真機 Debug 時始終運行不了的問題&#xff1a; 其實這已經是一個老問題了&#xff0c…

《R for Data Science (2e)》免費中文翻譯 (第1章) --- Data visualization(2)

寫在前面 本系列推文為《R for Data Science (2)》的中文翻譯版本。所有內容都通過開源免費的方式上傳至Github&#xff0c;歡迎大家參與貢獻&#xff0c;詳細信息見&#xff1a; Books-zh-cn 項目介紹&#xff1a; Books-zh-cn&#xff1a;開源免費的中文書籍社區 r4ds-zh-cn …

【機器學習【9】】評估算法:數據集劃分與算法泛化能力評估

文章目錄一、 數據集劃分&#xff1a;訓練集與評估集二、 K 折交叉驗證&#xff1a;提升評估可靠性1. 基本原理1.1. K折交叉驗證基本原理1.2. 邏輯回歸算法與L22. 基于K折交叉驗證L2算法三、棄一交叉驗證&#xff08;Leave-One-Out&#xff09;1、基本原理2、代碼實現四、Shuff…

CodeBuddy三大利器:Craft智能體、MCP協議和DeepSeek V3,編程效率提升的秘訣:我的CodeBuddy升級體驗之旅(個性化推薦微服務系統)

&#x1f31f; 嗨&#xff0c;我是Lethehong&#xff01;&#x1f31f; &#x1f30d; 立志在堅不欲說&#xff0c;成功在久不在速&#x1f30d; &#x1f680; 歡迎關注&#xff1a;&#x1f44d;點贊??留言收藏&#x1f680; &#x1f340;歡迎使用&#xff1a;小智初學計…

Spring Boot 整合 Redis 實現發布/訂閱(含ACK機制 - 事件驅動方案)

Spring Boot整合Redis實現發布/訂閱&#xff08;含ACK機制&#xff09;全流程一、整體架構二、實現步驟步驟1&#xff1a;添加Maven依賴<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

Sklearn 機器學習 線性回歸

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 Sklearn 機器學習線性回歸實戰詳解 線性回歸是機器學習中最基礎也最經典的算法之一,…

AJAX案例合集

案例一&#xff1a;更換網站背景JS核心代碼<script>document.querySelector(.bg-ipt).addEventListener(change, e > {//選擇圖片上傳&#xff0c;設置body背景const fd new FormData()fd.append(img, e.target.files[0])axios({url: http://hmajax.itheima.net/api/…

vscode環境下c++的常用快捷鍵和插件

本文提供一些能夠在vscode的環境下&#xff0c;提高c代碼書寫效率的快捷鍵&#xff0c;插件以及設置等等。 快捷鍵ctrlshiftx&#xff1a; 彈出插件菜單ctrlshiftp&#xff1a;彈出命令面板可以快捷執行一些常見命令插件安裝這個后&#xff0c;可以按住ctrl跳轉到方法的實現&am…

React + ts 中應用 Web Work 中集成 WebSocket

一、Web Work定義useEffect(() > {let webSocketIndex -1const websocketWorker new Worker(new URL(./websocketWorker.worker.ts?worker, import.meta.url),{type: module // 必須聲明模塊類型});//初始化WEBSOCKET&#xff08;多個服務器選擇最快建立連接…

RabbitMQ面試精講 Day 3:Exchange類型與路由策略詳解

【RabbitMQ面試精講 Day 3】Exchange類型與路由策略詳解 文章標簽 RabbitMQ,消息隊列,Exchange,路由策略,AMQP,面試題,分布式系統 文章簡述 本文是"RabbitMQ面試精講"系列第3天內容&#xff0c;深入解析RabbitMQ的核心組件——Exchange及其路由策略。文章詳細剖析…

深入解析Hadoop MapReduce Shuffle過程:從環形緩沖區溢寫到Sort與Merge源碼

MapReduce與Shuffle過程概述在大數據處理的經典范式MapReduce中&#xff0c;Shuffle過程如同人體血液循環系統般連接著計算框架的各個組件。作為Hadoop最核心的分布式計算模型&#xff0c;MapReduce通過"分而治之"的思想將海量數據處理分解為Map和Reduce兩個階段&…

Kafka MQ 消費者

Kafka MQ 消費者 1 創建消費者 在讀取消息之前,需要先創建一個KafkaConsumer對象。創建KafkaConsumer對象與創建KafkaProducer對象非常相似—把想要傳給消費者的屬性放在Properties對象里。本章后續部分將深入介紹所有的配置屬性。為簡單起見,這里只提供3個必要的屬性:boo…

人工智能——Opencv圖像色彩空間轉換、灰度實驗、圖像二值化處理、仿射變化

一、圖像色彩空間轉換&#xff08;一&#xff09;顏色加法1、直接相加1、直接相加2、調用cv.add()函數進行飽和操作 在OpenCV中進行顏色的加法&#xff0c;我們說圖像即數組&#xff0c;所以從數據類型來說我們可以直接用numpy的知識來進行直接相加&#xff0c;但是存在…

【JToken】JToken == null 判斷無效的問題

if (innerNode null) {continue; }Debug.Log($"toNode type: {node["toNode"]?.GetType()}");發現這個JToken 無法正確的判斷 是否為 null&#xff0c;再排除邏輯問題后&#xff0c;我基本能確定的是 這個對象 不返回的不是真正的C# NULL 輸出類型后是 N…

C++基于libmodbus庫實現modbus TCP/RTU通信

今天看到了一個參考項目中用到了modbus庫&#xff0c;看著使用很是方便&#xff0c;于是記錄一下。后面有時間了或者用到了再詳細整理。 參考&#xff1a;基于libmodbus庫實現modbus TCP/RTU通信-CSDN博客 一、介紹 1.1庫文件包含 1.2最簡單的使用 本人在QT6.5下&#xff0…

【原創】微信小程序添加TDesign組件

前言 TDesign 是騰訊公司推出的一款UI界面庫,至于騰訊的實力嘛,也不用多說了。 官網:https://tdesign.tencent.com/ 源碼:https://github.com/Tencent/tdesign 目前處于活躍狀態,發文前5日,該庫仍在更新中… 遇到的問題 雖然騰訊為微信小程序開發提供了一個討論的論壇,…

Vue的路由模式的區別和原理

路由模式 Vue 的路由模式指的是 Vue Router 提供的 URL 處理方式&#xff0c;主要有兩種&#xff1a;Hash 模式和History 模式。 Hash模式 在 Vue Router 中&#xff0c;默認使用的是 hash 模式&#xff0c;即 mode: hash。如果想要使用 history 模式&#xff0c;可以設置 mode…