網站首頁 編程語言 正文
GO的并發編程分享
之前我們分享了網絡編程,今天我們來看看GO的并發編程分享,我們先來看看他是個啥
啥是并發編程呢
指在一臺處理器上同時處理多個任務
此處說的同時,可不是同一個時間一起手拉手做同一件事情
并發是在同一實體上的多個事件,而這個事件在同一時間間隔發生的,同一個時間段,有多個任務執行,可是同一個時間點,只有一個任務在執行
為啥要有并發編程
隨著互聯網的普及,互聯網用戶人數原來越多,這對系統的性能帶來了巨大的挑戰。
我們要通過各種方式來高效利用硬件的性能(壓榨),從而提高系統的性能進而提升用戶體驗,提升團隊或者企業的競爭力。
并發是為了解決什么問題?目的是啥?
是充分的利用好處理器的每一個核,以達到最高的處理性能,盡可能的運用好每一塊磚
可是由于現在我們使用的CPU,內存,IO三者之間速度不盡相同
我們為了提高系統性能,計算機系統會將這三者速度進行平衡,以達到最優的效果,都有如下措施:
- 操作系統增加了進程、線程,以分時復用 CPU,進而均衡 CPU 與 I/O 設備的速度差異;
- CPU 增加了緩存,以均衡與內存的速度差異;
- 編譯程序優化指令執行次序,使得緩存能夠得到更加合理地利用。
說到進程和線程,他們都是干啥的呢,咱們順帶說一下?
進程是程序在操作系統中的一次執行過程
是 系統進行資源分配和調度的一個獨立單位。
線程是進程的一個執行實體
是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。
一個進程可以創建和撤銷多個線程, 并且同一個進程中的多個線程之間可以并發執行。
講到并發編程不得不說并發和并行有啥區別?是不是總是有小伙伴弄不清楚他們到底是啥區別,好像一樣,又好像不一樣
并發和并行的區別
一言蔽之,區別如下:
并發
多線程程序在一個核的 CPU 上運行
并行
多線程程序在多個核的 CPU 上運行
并發就像多個小伙伴跑接力,同一個時間點只會有一個小伙伴在跑,互相有影響
并行就像是多個小伙伴同一個起點一起跑,互不干擾
我們需要記住一點,再強調一波:
并發不是并行
并發主要由切換時間片來實現"同時"運行
并行則是直接利用多核實現多線程的運行,
在 GO 可以設置使用核數,以發揮多核計算機的能力,不過設置核數都是依賴于硬件的
那么,講到GO的并發編程,就必須上我們的主角,那就是協程
協程 goroutine 是啥
協程是一種程序組件
是由子例程(過程、函數、例程、方法、子程序)的概念泛化而來的
子例程只有一個入口點且只返回一次,而協程允許多個入口點,可以在指定位置掛起和恢復執行。
協程和線程分別有啥特點嘞
協程
獨立的棧空間,共享堆空間,調度由用戶自己控制
本質上有點類似于用戶級線程,這些用戶級線程的調度也是自己實現的。
線程
一個線程上可以跑多個協程,協程是輕量級的線程。
GO 高并發的原因是啥
-
goroutine
奉行通過通信來共享內存 - 每個一個GO的實例有
4~5KB
的棧內存占用,并且由于 GO 實現機制而大幅減少的創建和銷毀開銷 - Golang 在語言層面上就支持協程
goroutine
GOLANG并發編程涉及哪些知識點呢
- 基本協程的原理,實現方式,雖然說,GO中使用協程很方便,可以我們必須要知其然而值其所以然
- Goroutine 池
- runtime 包的使用
- Channel 通道
- 定時器
- 并發且安全的鎖
- 原子操作
- select 多路復用
- 等等...
Goroutine的那些事
我們寫C/C++
的時候,我們必然也是要實現并發編程
我們通常需要自己維護一個線程池,并且需要自己去包裝一個又一個的任務,同時需要自己去調度線程執行任務并維護上下文切換
且做線程池的時候,我們需要自己做一個線程管理的角色,靈活動態壓縮和擴容
可是能不能有這樣一種機制,我們只需要定義多個任務,讓系統去幫助我們把這些任務分配到CPU上實現并發執行
GO里面就正好有這樣的機制
goroutine
的概念類似于線程
goroutine
是由Go的運行時(runtime)調度和管理的
Go程序會智能地將 goroutine
中的任務合理地分配給每個CPU
Go 在語言層面已經內置了調度和上下文切換的機制
寫 GO 比較爽的一個地方是:
在GO里面,你不需要去自己寫進程、線程、協程
我們可以使用 goroutine 包
如何使用 goroutine
我們需要讓某個任務并發執行的時候,只需要把這個任務包裝成一個函數
專門開啟一個 goroutine 協程 去執行這個函數就可以了 , GO一個協程,很方便
一個 goroutine 必定對應一個函數,可以創建多個 goroutine 去執行相同的函數,只是多個協程都是做同一個事情罷了
我們先來使用一下協程,再來拋磚引玉,適當的分享一下
啟動單個協程
func Hi() { fmt.Println("this is Hi Goroutine!") } func main() { Hi() fmt.Println("main goroutine!") }
我們一般調用函數是如上這個樣子的,效果如下
this is Hi Goroutine!
main goroutine!
其實我們調用協程的話,也與上述類似
我們可以使用 go 后面加上函數名字,來開辟一個協程,專門做函數需要執行的事情
func main() { go Hi() // 啟動一個goroutine 協程 去執行 Hi 函數 fmt.Println("main goroutine!")
實際效果我們可以看到,程序只打印了 main goroutine!
main goroutine!
在程序啟動的時候,Go 程序就會為 main() 函數創建一個默認的 goroutine 協程
當 main() 函數返回的時候,剛開辟的另外一個 goroutine 協程 就結束了
所有在 main() 函數中啟動的 goroutine 協程 會一同結束,老大死了,其余的傀儡也灰飛煙滅了
我們也可以讓主協程等等一定子協程,待子協程處理完自己的事情,退出后,主協程再自己退出,這和我們寫C/C++的進程 和 線程的時候,類似
簡單的,我們可以使用 time.sleep
函數來讓主協程阻塞等待
我們也可以使用 上述提到的 使用 select{} 來達到目的
當然也有其他的方式,后續文章會慢慢的分享到
多個協程
那么多個協程又是怎么玩的呢?
我們使用 sync.WaitGroup 來實現goroutine 協程的同步
package main import ( "fmt" "sync" ) var myWg sync.WaitGroup func Hi(i int) { // goroutine 協程 結束就 記錄 -1 defer myWg.Done() fmt.Println("Hello Goroutine! the ", i) } func main() { for i := 0; i < 10; i++ { // 啟動一個goroutine 協程 就記錄 +1 myWg.Add(1) go Hi(i) } // 等待所有記錄 的goroutine 協程 都結束 myWg.Wait() }
會有如下輸出,每一個協程打印的數字并不是按照順序來的:
Hello Goroutine! the ?9
Hello Goroutine! the ?4
Hello Goroutine! the ?2
Hello Goroutine! the ?3
Hello Goroutine! the ?6
Hello Goroutine! the ?5
Hello Goroutine! the ?7
Hello Goroutine! the ?8
Hello Goroutine! the ?1
Hello Goroutine! the ?0
還是同樣的, 如果是主協程先退出,那么子協程還行繼續運行嗎?
毋庸置疑,主協程退出,子協程也會跟著退出
GO 中的協程
分享如下幾個點
GO中的棧是可增長的
一般都有固定的棧內存(通常為2MB),goroutine 的棧不是固定的,goroutine 的棧大小可以擴展到1GB
goroutine 是如何調度
這就不得不提 GPM
GPM是Go語言運行時(runtime)層面實現的,我們先簡單了解一下GPM分別代表啥
G
就是個 goroutine ,里面除了存放本 goroutine 信息外 還有與所在P的綁定等信息
P
Processor 管理著一組 goroutine 隊列
P 里面會存儲當前 goroutine 運行的上下文環境(函數指針,堆棧地址及地址邊界)
P 會對自己管理的 goroutine 隊列做一些調度(比如把占用CPU時間較長的 goroutine 暫停、運行后續的 goroutine)
當自己的隊列消費完了就去全局隊列里取,如果全局隊列里也消費完了會去其他P的隊列里搶任務。
M(machine)
是 Go 運行時(runtime)對操作系統內核線程的虛擬
M 與內核線程一般是一一映射的關系, 一個 groutine 最終是要放到 M上執行
這里的 P 與 M 一般也是一一對應的
P 管理著一組G 掛載在 M 上運行
當一個 G 長久阻塞在一個 M 上時,runtime 會新建一個M,
阻塞 G 所在的 P 會把其他的 G 掛載在新建的M上
這個時候,當舊的 G 阻塞完成或者認為其已經掛了的話,就會回收舊的 M
還有一點
P 的個數是通過 runtime.GOMAXPROCS
設定(最大256),這個數字也依賴于自己的硬件,在并發量大的時候會增加一些 P 和 M ,但不會太多
總結
- 分享了并發和并行
- 分享了GO 的并發,協程的簡單使用
- 簡單分享了GO可伸縮擴展的棧內存
原文鏈接:https://juejin.cn/post/6972547346039046157
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-08-14 Gradle?Build?Cache引發的Task緩存編譯問題_Android
- 2022-06-29 Oracle中case?when函數的用法_oracle
- 2022-08-25 高級消息隊列協議AMQP簡介_其它綜合
- 2022-06-09 ASP.NET?Core使用EF創建模型(包含屬性、排除屬性、主鍵和生成值)_實用技巧
- 2022-12-10 C++?vector與數組轉換寫入/讀出文件方式_C 語言
- 2022-09-29 淺談音視頻?pts?dts基本概念及理解_其它綜合
- 2023-01-20 python的程序分支結構用法及說明_python
- 2022-10-29 qt輸出自定義的pdf文件源碼詳解
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支