golang -- 如何讓main goroutine等一等

目錄

  • 引言
  • 一、sync.WaitGroup
  • 二、channel
    • 創建
    • channle操作
    • 緩沖
    • 多返回值模式
    • 單向通道

引言

在不做修飾的程序中,代碼是串行執行的

   串行、并發與并行串行:事物按照一定的發展順序并發:同一時間段執行多個任務(一邊吃飯一邊看電視)并行:同一時刻執行多個任務(你和你的好朋友都在學習go語言)

對于這樣一段代碼

func hello() {fmt.Println("hello")
}
func main() {hello()fmt.Println("world")
}

輸出

hello
world

如果啟動一個goroutine

func hello() {fmt.Println("hello")
}
func main() {go hello()fmt.Println("world")
}

輸出

world

為什么會出現這樣的結果
這是因為在創建goroutine時需要花費一定時間,在這段時間內,main goroutine是繼續執行的,如果main goroutine執行完成,就不會管其他的goroutine,程序直接退出,所以不會打印出hello

如果想要“hello”也打印出來,就要想辦法讓main goroutine 等一等

最簡單粗暴的方法就是調用time.Sleep函數,延遲結束程序

func hello() {fmt.Println("hello")
}
func main() {hello()fmt.Println("world")time.Sleep(time.Second)
}

編譯執行后打印

world
hello

為什么先打印world?
同樣的,創建goroutine時需要花費一定時間,在這段時間內,main goroutine繼續執行,理解為因為創建goroutine時花費了時間,所以goroutine執行起來比main goroutine慢

但是使用這種方法存在一定的問題,因為不知道程序執行具體需要多長時間
如果sleep時間過長,可能會存在 一段時間內,程序沒有執行任務 的情況,這樣就降低了效率
如果sleep時間過短,還是有可能打印不出goroutine中的語句


要更好的解決這個問題,有三種方法可以使用

  1. sync.WaitGroup
  2. channel
  3. Context

先不說第三種,因為還不會


一、sync.WaitGroup

使用sync.WaitGroup優化上面程序的代碼是

var wg sync.WaitGroup //聲明等待組變量func hello() {fmt.Println("hello")wg.Done() //當前goroutine執行完畢
}func main() {wg.Add(1) //記錄需要等待的goroutine數量go hello()fmt.Println("world")wg.Wait() // 等待直到所有goroutine執行完成
}

編譯執行后打印

world
hello
  • sync.WaitGroup定義
type WaitGroup struct {noCopy noCopystate atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.sema  uint32
}

不難而見,WaitGroup是一個結構體
sync包中提供了三種WaitGroup的方法

方法名功能
func (wg *WaitGroup) Add(delta int)記錄要等待的協程的數量
func (wg *WaitGroup) Done()記錄當前協程已執行完畢
func (wg *WaitGroup) Wait()等待子協程結束,否則阻塞

sync.WaitGroup內部可以理解為一個計數器,計數器的值可以增加和減少。
例如當我們啟動了 N 個并發任務時,就將計數器值增加N;
每個任務完成時通過調用 Done 方法將計數器減1;
通過調用 Wait 來等待并發任務執行完,當計數器值為 0 時,表示所有并發任務已經完成

舉一個通俗的例子,這里將計數器稱為count
你室友喊你去打游戲,你給自己定了兩個目標:復習二重積分和三重積分,完成這兩個目標就去和室友打游戲
首先,你給自己定了兩個目標(count = 2),復習完二重積分后,你還剩下一個目標沒有完成(count = 1),這時候你的室友還要繼續等你(wait),好了,又把三重積分復習完了(count = 0),這時候你就和室友去打游戲了

WaitGroup 通常適用于可動態調整協程數量的時候,例如事先知道協程的數量,又或者在運行過程中需要動態調整。

WaitGroup是結構體,作為函數參數傳參時,應該傳遞指針而不是值;如果傳遞值,只是將WaitGroup拷貝后,對拷貝的WaitGroup進行修改,不會改變真正的WaitGroup的值,這可能會導致主協程一直阻塞等待,程序將無法正常運行


二、channel

這一部分是我在學習channel 時候的筆記加上自己的理解
參考🔗李文周的博客-Go語言基礎之并發 🔗Golang 中文學習文檔-并發

創建

  1. 聲明
var 變量名稱 chan 元素類型
chan:關鍵字
元素類型:是指通道中傳遞元素的類型
	var ch chan int //傳遞整型的通道var ch1 chan string  //傳遞string類型的通道

沒有初始化之前通道是對應類型的零值

  1. 創建

用make創建

	ch1 := make(chan int)      //沒有緩沖區(下面說緩沖)ch2 := make(chan int, 1)  //緩沖區為1
  1. 關閉

使用內置的 close 關閉通道

close定義:

func close(c chan<- Type)

關閉ch通道:

close(ch)

用戶必須發出一個關閉通道的指令,通道才會關閉
與文件操作不同,文件在結束操作后必須關閉,但是通道不必須關閉


channle操作

通道的操作有 發送(send)、接收(receive)和關閉(close)。
發送和接收都用符號 ‘<-’

  1. 發送

ch <-:表示對一個通道寫入數據

ch <- 5 // 把5發送到ch中
  1. 接收

<- ch:表示對一個通道讀取數據(直接看箭頭指向區分這兩種操作就可以)

  • 單返回值
x := <- ch // 從ch中接收值并賦值給變量x
<-ch       // 從ch中接收值,忽略結果
  • 雙返回值
value, ok := <-ch

value:通道中的值,如果被關閉返回對應的零值
ok:布爾類型的值,通道關閉時返回false,否則返回true

雙返回值還可以用來判斷通道是否關閉

  • 判斷通道是否被關閉:
    示例:value, ok := <-ch
    value:從通道中取出的值,如果通道被關閉則返回對應類型的零值
    ok:通道ch關閉時返回false,否則返回true

  • for range 接收值
    通常用for range循環從通道中接收值
    如果通道被關閉,會 在通道內的所有制被接收完畢后 自動退出循環
    如果沒有關閉,使用for range執行時會出錯

	ch4 := make(chan int, 4)ch4 <- 1ch4 <- 2ch4 <- 3ch4 <- 4close(ch4)for value := range ch4 {fmt.Println(value)}}func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}

輸出

1
2
3
4

通道的發送接收操作可以理解為一個容器
如果容器有空間,就可以把物品放進容器;
如果容器空間滿了,在容器中取出物品后,容器空間又有剩余,又可以把其他物品放入容器


關閉后的通道有以下特點:

  1. 對一個關閉的通道再發送值就會導致panic
  2. 對一個關閉的通道繼續接收會一直獲取值直到通道為空
  3. 對一個關閉的并且沒有值的通道執行接收操作會得到對應類型的零值
  4. 關閉一個已經關閉的通道會導致panic
  5. 對已經關閉的通道再執行close也會引發panic

緩沖

  • 無緩沖的通道(阻塞的通道)
	ch1 := make(chan int)  //ch1是一個無緩沖的通道ch1 <- 10fmt.Println("發送成功")

go fatal error: all goroutines are asleep - deadlock!

deadlock -- 表示程序中的goroutine都被掛起導致程序死鎖了對一個無緩沖區通道執行發送操作,會發生阻塞對一個無緩沖通道執行接收操作,沒有任何向通道中發送值的操作也會導致接受操作阻塞

應對阻塞通道的方法

  1. 創建goroutine
    func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)}func main(){ch2 := make(chan int)go recv(ch2)ch2 <- 10 // 發送操作fmt.Println("發送成功")close(ch2)//fmt.Println(<-ch2) -- 報錯了}
這段代碼的過程
case1:如果先進行發送操作,發生堵塞,直到另一個goroutine執行接收操作
case2:如果先進行接收操作,發生堵塞,直到另一個goroutine執行發送操作
  1. 使用有緩沖的通道
	ch3 := make(chan int, 1)ch3 <- 1fmt.Println("發送成功 ")x1 := <-ch3fmt.Println(x1)  //輸出1ch3 <- 2close(ch3)//對關閉的通道執行接收操作,會直到取完通道中的元素num0 := len(ch3)  //len--獲取通道中元素個數x2 := <-ch3num := len(ch3)fmt.Println(num0, x2, num) //輸出 1 2 0
只要通道的容量大于0,那就是有緩沖的通道,通道的容量表示通道中最大能存放的元素數量。
當通道內一鈾元素達到最大容量后,再向通道中執行發送操作就會阻塞(如果接收后再發送就不會了,相當于清空了)
len -- 獲取通道內元素的數量
cap -- 獲取通道的容量

多返回值模式

  • 判斷通道是否被關閉:
    示例:value, ok := <-ch
    value:從通道中取出的值,如果通道被關閉則返回對應類型的零值
    ok:通道ch關閉時返回false,否則返回true

  • for range 接收值
    通常用for range循環從通道中接收值
    如果通道被關閉,會 在通道內的所有制被接收完畢后 自動退出循環
    如果沒有關閉,使用for range執行時會出錯

	ch4 := make(chan int, 4)ch4 <- 1ch4 <- 2ch4 <- 3ch4 <- 4close(ch4)for value := range ch4 {fmt.Println(value)}}func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}

輸出

1
2
3
4

單向通道

現在有兩個函數
producer 函數 返回通道,并且執行發送操作
consume r函數從通道中接收值進行計算

func Producer() chan int {ch := make(chan int) //有沒有緩沖值都可以//創建一個新的goroutine執行發送數據的任務go func() {for i := 0; i < 5; i++ {ch<-i}}close(ch)}()return ch
}func Consumer(ch chan int) int {sum := 0for value := range ch {sum += value}return sum
}

上面的代碼沒辦法阻止在接收通道中執行發送操作,同理,沒辦法阻止在發送通道中執行接收操作
可以 限制參數或者返回值 來限制函數

<-chan int //只接收通道,只能接收不能發送
chan <-int //只發送通道,只能發送不能接收

改寫成

func Producer1() <-chan int { //發送操作ch := make(chan int) //有沒有緩沖值都可以//創建一個新的goroutine執行發送數據的任務go func() {for i := 0; i < 10; i++ {//有 1 3 5 7 9if i%2 == 1 {ch <- i}}close(ch)}()return ch
}func Consumer1(ch <-chan int) int { //接收操作sum := 0for value := range ch {sum += value}return sum
}func main() {//在函數傳參及任何賦值操作中全向通道(正常通道)可以轉換為單向通道,但是沒辦法反向轉換(單項通道沒辦法轉換成全向通道)ch1 := make(chan int, 1)ch1 <- 10close(ch1)Consumer1(ch1) //在傳參時將ch1轉為單項通道ch2 := make(chan int, 1)ch2 <- 4           //向ch2中發送4var ch3 <-chan int //聲明一個通道 只接收ch3 = ch2          //變量賦值時將ch2轉換為單向通道<-ch3              //接收操作
}

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

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

相關文章

第31講 循環緩沖區與命令解析

串口在持續接收數據時容易發生數據黏包&#xff08;先接收的數據尚未被處理&#xff0c;后面的數據已經將內存覆蓋&#xff09;的情況&#xff0c;循環緩沖區的本質就是將串口接受到的數據馬上拷貝到另外一塊內存之中。為了避免新來的數據覆蓋掉尚未處理的數據&#xff0c;一方…

UE 材質基礎 第一天

課程&#xff1a;虛幻引擎【UE5】材質寶典【初學者材質基礎入門系列】-北冥沒有魚啊_-稍后再看-嗶哩嗶哩視頻 隨便記錄一些 黑色是0到負無窮&#xff0c;白色是1到無窮 各向異性 有點類似于高光&#xff0c;可以配合切線來使用&#xff0c;R G B 相當于 X Y Z軸&#xff0c;切…

編譯原理--期末復習

本文是我學習以下博主視頻所作的筆記&#xff0c;寫的不夠清晰&#xff0c;建議大家直接去看這些博主的視頻&#xff0c;他/她們講得非常好&#xff1a; 基礎知識概念&#xff1a; 1.【【編譯原理】期末復習 零基礎自學】&#xff0c;資料 2.【編譯原理—混子速成期末保過】&…

【DeepSeek論文精讀】11. 洞察 DeepSeek-V3:擴展挑戰和對 AI 架構硬件的思考

歡迎關注[【AIGC論文精讀】](https://blog.csdn.net/youcans/category_12321605.html&#xff09;原創作品 【DeepSeek論文精讀】1. 從 DeepSeek LLM 到 DeepSeek R1 【DeepSeek論文精讀】7. DeepSeek 的發展歷程與關鍵技術 【DeepSeek論文精讀】11. 洞察 DeepSeek-V3&#xff…

寶塔面板部署前后端項目SpringBoot+Vue2

這篇博客主要用來記錄寶塔部署前端后端項目的過程。因為寶塔部署有點麻煩&#xff0c;至少在我看來挺麻煩的。我還是喜歡原始的ssh連接服務器進行操作。但是公司有項目用到了寶塔&#xff0c;沒辦法啊&#xff0c;只能摸索記錄一下。 我們需要提前準備好后端項目的jar包和前端項…

電機試驗平臺:創新科技推動電動機研究發展

電機試驗平臺是電機制造和研發過程中不可或缺的重要設備&#xff0c;其功能涵蓋了電機性能測試、電機壽命測試、電機質量評估等多個方面。隨著科技的不斷發展和電機應用領域的日益擴大&#xff0c;對電機試驗平臺的要求也越來越高。本文將從現代化電機試驗平臺的設計與應用兩個…

LangGraph 7 - Platform - Agentic RAG、監督、SQL代理、追蹤、私密對話、認證、RemoteGraph、LangSmith

文章目錄 代理式檢索增強生成&#xff08;Agentic RAG&#xff09;安裝 1、預處理文檔2、創建檢索器工具3、生成查詢4、文檔分級5、問題重寫6、生成答案7、構建流程圖8、運行智能RAG代理 多智能體監督系統安裝配置1、創建工作代理研究代理數學代理工具 2、使用 langgraph-super…

生命之樹--樹形dp

1.樹形dp--在dfs遍歷樹的同時dp&#xff0c;從上到下遞歸&#xff0c;到葉子是邊界條件 https://www.luogu.com.cn/problem/P8625 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long long ll; typedef pair<ll,int> pii; int n,c; ll …

10.8 LangChain三大模塊深度實戰:從模型交互到企業級Agent工具鏈全解析

LangChain Community 項目:Model I/O, Retrieval, Agent Tooling 關鍵詞:LangChain Model I/O, 檢索增強生成, Agent 工具鏈, 多路召回策略, 工具調用協議 1. Model I/O 模塊:大模型交互標準化接口 Model I/O 是 LangChain 生態中連接大模型的核心模塊,定義了統一的輸入輸…

鴻蒙OSUniApp 實現圖片上傳與壓縮功能#三方框架 #Uniapp

UniApp 實現圖片上傳與壓縮功能 前言 在移動應用開發中&#xff0c;圖片上傳是一個非常常見的需求。無論是用戶頭像、朋友圈圖片還是商品圖片&#xff0c;都需要上傳到服務器。但移動設備拍攝的圖片往往尺寸較大&#xff0c;直接上傳會導致流量消耗過大、上傳時間過長&#x…

已經裝了pygame但pycharm顯示沒有該模塊/軟件包無法加載出來下載pygame

首先&#xff0c;如果你已經通過pip install pygame或者其他什么命令下載好了pygame &#xff08;可以通過pip list查看&#xff0c;有pygame說明pygame已經成功安裝在當前python環境中&#xff09;。然而&#xff0c;如果你在 PyCharm 中仍然看不到 pygame&#xff0c;可能是因…

第6章 實戰案例:基于 STEVAL-IDB011V1 板級 CI/CD 全流程

在前五章中,我們完成了嵌入式 CI/CD 從環境搭建、編譯自動化、測試自動化、發布分發到監控回歸的全技術鏈條。本章將以 STEVAL-IDB011V1(搭載 BlueNRG-355)評估板為實戰載體,手把手演示如何在 GitLab CI(或 Jenkins)上,構建一條從 Git Push → 編譯 → 測試 → 刷寫 → …

系統架構設計(十四):解釋器風格

概念 解釋器風格是一種將程序的每個語句逐條讀取并解釋執行的體系結構風格。程序在運行時不會先被編譯為機器碼&#xff0c;而是動態地由解釋器分析并執行其語義。 典型應用&#xff1a;Python 解釋器、JavaScript 引擎、Bash Shell、SQL 引擎。 組成結構 解釋器風格系統的…

1-機器學習的基本概念

文章目錄 一、機器學習的步驟Step1 - Function with unknownStep2 - Define Loss from Training DataStep3 - Optimization 二、機器學習的改進Q1 - 線性模型有一些缺點Q2 - 重新詮釋機器學習的三步Q3 - 機器學習的擴展Q4 - 過擬合問題&#xff08;Overfitting&#xff09; 一、…

SQL里where條件的順序影響索引使用嗎?

大家好&#xff0c;我是鋒哥。今天分享關于【SQL里where條件的順序影響索引使用嗎&#xff1f;】面試題。希望對大家有幫助&#xff1b; SQL里where條件的順序影響索引使用嗎&#xff1f; 1000道 互聯網大廠Java工程師 精選面試題-Java資源分享網 在 SQL 查詢中&#xff0c;W…

計算機科技筆記: 容錯計算機設計05 n模冗余系統 TMR 三模冗余系統

NMR&#xff08;N-Modular Redundancy&#xff0c;N 模冗余&#xff09;是一種通用的容錯設計架構&#xff0c;通過引入 N 個冗余模塊&#xff08;N ≥ 3 且為奇數&#xff09;&#xff0c;并采用多數投票機制&#xff0c;來提升系統的容錯能力與可靠性。單個模塊如果可靠性小于…

中級網絡工程師知識點7

1.存儲區城網絡SAN可分為IP-SAN,FC-SAN兩種&#xff0c;從部署成本和傳輸效率兩個方面比較兩種SAN&#xff0c;比較結果為FCSAN部署成本更高 2.RAID2.0技術的優勢&#xff1a; &#xff08;1&#xff09;自動負載均衡&#xff0c;降低了存儲系統故障率 &#xff08;2&#x…

在Ubuntu24.04中配置開源直線特征提取軟件DeepLSD

在Ubuntu24.04中配置開源直線特征提取軟件DeepLSD 本文提供在Ubuntu24.04中配置開源直線特征提取軟件DeepLSD的基礎環境配置、列出需要修改的文件內容&#xff0c;以及報錯解決方案集錦。 基礎的編譯安裝環境 python3.8.12CUDA12gcc/g 9.5&#xff08;系統自帶的g-13版本太新…

Nginx+Lua 實戰避坑:從模塊加載失敗到版本沖突的深度剖析

Nginx 集成 Lua (通常通過 ngx_http_lua_module 或 OpenResty) 為我們提供了在 Web 服務器層面實現動態邏輯的強大能力。然而,在享受其高性能和靈活性的同時,配置和使用過程中也常常會遇到各種令人頭疼的問題。本文將結合實際案例,深入分析在 Nginx+Lua 環境中常見的技術問題…

Vue.js組件開發進階

Vue.js 是一個漸進式 JavaScript 框架&#xff0c;廣泛用于構建用戶界面。組件是 Vue.js 的核心概念之一&#xff0c;允許開發者將 UI 拆分為獨立、可復用的模塊。本文將深入探討 Vue.js 組件的開發&#xff0c;涵蓋從基礎到高級的各個方面。 組件的基本概念 在 Vue.js 中&am…