詳解Go語言中如何通過Goroutine實現(xiàn)高并發(fā)
一、并發(fā)編程的基本概念
并發(fā)編程是指在一個程序中同時運行多個任務,這些任務可以獨立地執(zhí)行,也可以相互協(xié)作。并發(fā)編程可以提高程序的執(zhí)行效率,特別是在處理大量I/O操作或計算密集型任務時。
在Go語言中,并發(fā)編程主要通過goroutine和channel來實現(xiàn)。
Goroutine是Go語言獨有的并發(fā)執(zhí)行單元,它允許函數(shù)或方法并發(fā)執(zhí)行,而無需手動管理線程。
Channel是Go語言中進行goroutine間通信和同步的主要機制。
二、進程、線程、協(xié)程
**程序:**指令和數(shù)據(jù)的一個有序集合。本身沒有任何含義,是一個靜態(tài)的概念。
進程:QQ.exe 微信 … 一個個的程序、執(zhí)行起來之后,開啟一個進程。執(zhí)行程序的一次執(zhí)行過程,它是動態(tài)的概念。進程是系統(tǒng)資源分配的單位。
**線程:**一個進程中可以有多個線程,并行的,一個進程之中,至少要有一個線程。main 主線程
- 線程是CPU調(diào)度和執(zhí)行的單位。
- 一個線程,直接執(zhí)行就可以了
- 多個線程:CPU如何調(diào)度執(zhí)行。 一個CPU、也是可以跑多個線程的。
并行和并發(fā)
- 并發(fā):一個cpu同一時間不停執(zhí)行多個程序
- 并行:多個cpu同一時間不停執(zhí)行多個程序
在代碼級別中的所謂多線程并發(fā)處理問題。模擬出來的。
- 真正的多線程是指的擁有多個CPU、多核
- 如果是模擬出來的多線程、即在一個CPU的情況下,在同一個時間點,只能執(zhí)行一個線程的代碼。
- 因為執(zhí)行的速度很快,所以就有了同時在執(zhí)行的一種錯覺。
并行真的就最快嗎?
- 并行運行的組件,考慮到多個線程之間的通信問題,這種跨CPU通信問題是開銷比較高的,并行并不一定快。
進程
進程是一個程序在一個數(shù)據(jù)集中的一次動態(tài)執(zhí)行過程,可以簡單理解為“正在執(zhí)行的程序",它是CPU資源分配和調(diào)度的獨立單位。
進程一般由程序、數(shù)據(jù)集、進程控制塊三部分組成。我們編寫的程序用來描述進程要完成哪些功能以及如何完成;
數(shù)據(jù)集則是程序在執(zhí)行過程中所需要使用的資源;進程控制塊用來記錄進程的外部特征,描述進程的執(zhí)行變化過程,系統(tǒng)可以利用它來控制和管理進程,它是系統(tǒng)感知進程存在的唯一標志。 進程的局限是創(chuàng)建、撤銷和切換的開銷比較大。
線程 :
線程是在進程之后發(fā)展出來的概念。線程也叫輕量級進程,它是一個基本的CPU執(zhí)行單元,也是程序執(zhí)行過程中的最小單元,由線程ID、 程序計數(shù)器、寄存器集合和堆棧共同組成。一個進程可以包含多個線程。
線程的優(yōu)點是減小了程序并發(fā)執(zhí)行時的開銷,提高了操作系統(tǒng)的并發(fā)性能,**缺點是線程沒有自己的系統(tǒng)資源,同一進程的各線程可以共享進程所擁有的系統(tǒng)資源,如果把進程比作一個車間,那么線程就好比是車間里面的工人。**不過對于某些獨占性資源存在鎖機制,處理不當可能會產(chǎn)生”死鎖"。
協(xié)程Goroutine
協(xié)程是一種用戶態(tài)的輕量級線程,又稱微線程,英文名Coroutine,協(xié)程的調(diào)度完全由用戶控制。人們通常將協(xié)程和子程序(函數(shù))比較著理解。
就好比是啟動了一個函數(shù),單次執(zhí)行完畢它。不影響我們main線程的執(zhí)行。
子程序調(diào)用總是一個入口,一次返回,一旦退出即完成了子程序的執(zhí)行。
與傳統(tǒng)的系統(tǒng)級線程和進程相比,協(xié)程的最大優(yōu)勢在于其"輕量級”,可以輕松創(chuàng)建上百萬個而不會導致系統(tǒng)資源衰竭,而線程和進程通常最多也不能超過1萬的。這也是協(xié)程也叫輕量級線程的原因。
補充點:Go語言流行的主要一個原因,高并發(fā)的問題。高效!
Go語言對于并發(fā)的實現(xiàn)是靠協(xié)程,Goroutine
三、Goroutine
Go中使用Goroutine來實現(xiàn)并發(fā)concurrently
**Goroutine是Go語言特有的名詞。**區(qū)別于進程Process,線程Thread, 協(xié)程Goroutine, 因為Go語言的創(chuàng)造者們覺得和他們是有所區(qū)別的,所以專門創(chuàng)造了Goroutine
Goroutine是與其他函數(shù)或方法同時運行的函數(shù)或方法。Goroutines可以被認為是輕量級的線程。與線程相比,創(chuàng)建Goroutine的成本很小,它就是一段代碼,一個函數(shù)入口。以及在堆上為其分配的一個堆棧(初始大小為4K,會隨著程序的執(zhí)行自動增長刪除)。因此它非常廉價,Go應用程序可以輕松并發(fā)運行數(shù)千個Goroutines
在go語言中使用 goroutine,在調(diào)用函數(shù)或者方法前面加上 go 關鍵字即可。
普通方法調(diào)用 對比 多線程
**普通方法調(diào)用:**串行
main(){ // 串行執(zhí)行 1/2/3 test1() test2() test3() }
多線程
main(){ // 4個線程同時執(zhí)行:main test1 test2 test3 、交替的快速執(zhí)行。 go test1() go test2() go test3() }
四、Goroutine的創(chuàng)建和使用
Goroutine是由Go的運行時調(diào)度和管理的輕量級線程。每個goroutine都有自己獨立的??臻g,并且由Go的運行時環(huán)境進行調(diào)度。
與線程不同,goroutine的創(chuàng)建和切換成本更低,因為它只是函數(shù)入口和堆棧的封裝,不需要像線程那樣進行復雜的上下文切換。
1. 創(chuàng)建Goroutine
在Go語言中,使用go關鍵字來創(chuàng)建一個新的goroutine。例如:
package main import ( "fmt" "time" ) func main() { //使用go關鍵字來創(chuàng)建goroutine協(xié)程 go Hello() time.Sleep(1 * time.Second) // 等待一秒鐘,確保Hello函數(shù)有足夠的時間執(zhí)行。如果不加等待,main函數(shù)如果結束了,所有的 goroutine也會瞬間銷毀 } func Hello() { fmt.Println("hello world") }
2. Goroutine的規(guī)則
1、當新的Goroutine開始時, Goroutine調(diào)用立即返回。與函數(shù)不同,go不等待Goroutine執(zhí)行結束
2、當Goroutine調(diào)用,并且Goroutine的任何返回值被忽略之后,go立即執(zhí)行到下一行代碼
3、main的Goroutine應該為其他的Goroutines執(zhí)行。如果main的Goroutine終止了,程序將被終止,而其他Goroutine將不會運行
五、Goroutine的生命周期和調(diào)度
Go程序在一個主Goroutine中啟動,這個主Goroutine封裝了main函數(shù)。主Goroutine會進行一系列的初始化工作,包括設置每個goroutine能申請的??臻g的最大尺寸、啟動垃圾回收Goroutine等。
當使用go關鍵字創(chuàng)建一個新的Goroutine時,Go的運行時會創(chuàng)建一個新的輕量級線程,并分配一個Goroutine棧,然后將該Goroutine添加到調(diào)度器中。
調(diào)度器會根據(jù)調(diào)度算法選擇一個可用的Goroutine運行。如果當前沒有可用的Goroutine,程序可能會進入休眠狀態(tài)。
Goroutine在調(diào)用某些會引起阻塞的函數(shù)時,會被暫停,直到函數(shù)返回結果。
這些函數(shù)包括I/O操作、網(wǎng)絡請求、系統(tǒng)調(diào)用、鎖等。
當Goroutine阻塞時,調(diào)度器會將它放回到隊列中,直到它再次準備好運行。
主Goroutine - mian
封裝main函數(shù)的goroutine稱為主goroutine。
主goroutine所做的事情并不是執(zhí)行main函數(shù)那么簡單。它首先要做的是:設定每一個goroutine所能申請的棧空間的最大尺寸。
在32位的計算機系統(tǒng)中此最大尺寸為250MB,而在64位的計算機系統(tǒng)中此尺寸為1GB。
如果有某個goroutine的??臻g尺寸大于這個限制,那么運行時系統(tǒng)就會引發(fā)一個棧溢出(stack overflow)的運行時恐慌。隨后,這個go程序的運行也會終止。
此后,主goroutine會 進行一系列的初始化工作,涉及的工作內(nèi)容大致如下:
1、創(chuàng)建一個特殊的defer語句,用于在主goroutine退出時做必要的善后處理。因為主goroutine也可能非正常的結束
2、啟動專用于在后臺清掃內(nèi)存垃圾的goroutine,并設置GC可用的標識.
3、執(zhí)行main包中所引用包下的init函數(shù)
4、執(zhí)行main函數(shù)
執(zhí)行完main函數(shù)后,它還會檢查主goroutine是否引發(fā)了運行時恐慌,并進行必要的處理。
程序運行完畢后,主goroutine會結束自己以及當前進程的運行。
六、使用runtime包管理Goroutine
Go語言的runtime包提供了與Go運行時環(huán)境交互的各種功能,包括垃圾回收、并發(fā)控制、程序退出、堆棧管理等。以下是一些常用的runtime包函數(shù):
- runtime.GOMAXPROCS:設置最大可運行的操作系統(tǒng)線程數(shù)。
- runtime.NumCPU:返回機器的CPU核心數(shù)。
- runtime.NumGoroutine:返回當前運行的Goroutine數(shù)量。
- runtime.Gosched:讓出CPU時間片,使得其他Goroutine可以運行。
- runtime.Goexit:退出當前的Goroutine。
- runtime.KeepAlive:確保某個Goroutine不會被垃圾回收。
- runtime.SetFinalizer:為對象設置終結器,當垃圾回收器準備回收該對象時,會調(diào)用該終結器。
- runtime.GC:強制運行垃圾回收器。
- runtime.GOOS: 獲取操作系統(tǒng)名稱
這些函數(shù)可以幫助我們更好地管理Goroutine和并發(fā)編程。
例如,可以使用runtime.GOMAXPROCS來設置最大可運行的操作系統(tǒng)線程數(shù),從而控制并發(fā)度。
可以使用runtime.NumGoroutine來監(jiān)控當前運行的Goroutine數(shù)量,以便進行性能調(diào)優(yōu)。
獲取系統(tǒng)的信息runtime
package main import ( "fmt" "runtime" ) // 獲取系統(tǒng)的信息runtime func main() { // 獲取goRoot目錄 : 找到指定目錄,存放一些項目信息。 fmt.Println("GoRoot Path:", runtime.GOROOT()) // 獲取操作系統(tǒng) windows ,可以根據(jù)操作系統(tǒng)類型判斷盤符分隔符。 “\\” “/” fmt.Println("System:", runtime.GOOS) // 獲取cpu數(shù)量 12, 可以嘗試做一些系統(tǒng)優(yōu)化,開啟更大的棧空間。 fmt.Println("Cpu num:", runtime.NumCPU()) }
并發(fā)控制
package main import ( "fmt" "runtime" ) // 控制并發(fā)順序 func main() { // goroutine是競爭cpu的 ,調(diào)度 go func() { for i := 0; i < 5; i++ { fmt.Println("goroutine", i) } }() for i := 0; i < 5; i++ { // gosched:禮讓, 讓出時間片,讓其他的 goroutine 先執(zhí)行 // cpu是隨機,相對來說,可以讓一下,但是不一定能夠成功 // schedule runtime.Gosched() fmt.Println("main-", i) } }
正常情況下,main主函數(shù)里面的協(xié)程一般是先于go func()執(zhí)行的,但是我們在main函數(shù)里面加上了runtime.Gosched(),這樣main函數(shù)里面的代碼執(zhí)行就讓出CPU時間片,讓其他Goroutine先執(zhí)行
Goroutine的終止和清理
使用runtime.Goexit()終止Goroutine
runtime.Goexit()函數(shù)用于終止當前Goroutine的執(zhí)行。當調(diào)用Goexit()時,當前Goroutine會立即停止執(zhí)行,并把控制權交還給調(diào)度器。
這意味著當前Goroutine不會繼續(xù)執(zhí)行后續(xù)的代碼,也不會返回到調(diào)用它的地方。同時,其他仍在運行的Goroutine將繼續(xù)執(zhí)行。例如:
package main import ( "fmt" "runtime" "time" ) func main() { go func() { defer fmt.Println("A.defer") func() { defer fmt.Println("B.defer") //return // 只是終止了函數(shù) //這里設置,終止當前的 goroutine,該Goroutine中下面的代碼不再執(zhí)行 runtime.Goexit() defer fmt.Println("C.defer") fmt.Println("B") }() fmt.Println("A") }() time.Sleep(1 * time.Second) }
七、多線程會遇到的問題
1. 臨界資源的安全問題
臨界資源:指并發(fā)環(huán)境中多個進程、線程、協(xié)程共享的資源
在并發(fā)編程中對臨界資源的處理不當,往往會導致數(shù)據(jù)不一致的問題。
package main import ( "fmt" "time" ) func main() { // 此時的a就是臨界資源:多個協(xié)程共享的變量,會導致程序結果未知 a := 1 go func() { a = 2 fmt.Println("goroutine a:", a) }() a = 3 time.Sleep(3 * time.Second) fmt.Println("main a:", a) }
2. 經(jīng)典案例:售票問題
并發(fā)本身并不復雜,但是因為有了資源競爭的問題,就使得我們開發(fā)出好的并發(fā)程序變得復雜起來,因為會引起很多莫名其妙的問題。
如果多個goroutine在訪問同一個數(shù)據(jù)資源的時候,其中一個線程修改了數(shù)據(jù),那么這個數(shù)值就被修改了,對于其他的goroutine來講,這個數(shù)值可能是不對的。
package main import ( "fmt" "time" ) // 定義全局變量 票庫存為10張 var ticket int = 10 func main() { // 單線程不存在問題,多線程資源爭搶就出現(xiàn)了問題 //多人搶票 go saleTickets("張三") go saleTickets("李四") go saleTickets("王五") go saleTickets("趙六") time.Sleep(time.Second * 5) } // 售票函數(shù) func saleTickets(name string) { for { if ticket > 0 { time.Sleep(time.Millisecond * 1) fmt.Println(name, "剩余票的數(shù)量為:", ticket) //每賣出一張票,票的數(shù)量減一 ticket-- } else { fmt.Println("票已售完") break } } }
多線程都在操作數(shù)據(jù),出現(xiàn)了負數(shù)這種不合理的結果
發(fā)現(xiàn)結果和預想的不同,多線程加入之后,原先單線程的邏輯出現(xiàn)了問題。
以上就是詳解Go語言中如何通過Goroutine實現(xiàn)高并發(fā)的詳細內(nèi)容,更多關于Go Goroutine實現(xiàn)高并發(fā)的資料請關注腳本之家其它相關文章!
- Golang 語言控制并發(fā) Goroutine的方法
- Go并發(fā)編程之goroutine使用正確方法
- Go并發(fā)的方法之goroutine模型與調(diào)度策略
- Go語言中的并發(fā)goroutine底層原理
- Go語言使用goroutine及通道實現(xiàn)并發(fā)詳解
- GoLang并發(fā)機制探究goroutine原理詳細講解
- Golang并發(fā)繞不開的重要組件之Goroutine詳解
- Go中Goroutines輕量級并發(fā)的特性及效率探究
- golang并發(fā)編程中Goroutine 協(xié)程的實現(xiàn)
- Go 并發(fā)編程Goroutine的實現(xiàn)示例
相關文章
Go語言模擬while語句實現(xiàn)無限循環(huán)的方法
這篇文章主要介紹了Go語言模擬while語句實現(xiàn)無限循環(huán)的方法,實例分析了for語句模擬while語句的技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-02-02Golang無限緩存channel的設計與實現(xiàn)解析
這篇文章主要為大家介紹了Golang無限緩存channel的設計與實現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07