Golang中g(shù)oroutine和channel使用介紹深入分析
1.goroutine-看一個(gè)需求
需求:要求統(tǒng)計(jì)1-900000000的數(shù)字中,那些是素?cái)?shù)?
分析:
- 傳統(tǒng)方法,就是使用一個(gè)循環(huán),循環(huán)的判斷各個(gè)數(shù)是不是素?cái)?shù)。
- 使用并發(fā)或并行的方式,將統(tǒng)計(jì)素?cái)?shù)的任務(wù)分配給多個(gè)goroutine去完成,這時(shí)就會(huì)使用到goroutine。
2.進(jìn)程和線程介紹
- 進(jìn)程就是程序在操作系統(tǒng)中的一次執(zhí)行過(guò)程,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位
- 線程是進(jìn)程的一個(gè)執(zhí)行實(shí)例,是程序執(zhí)行的最小單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位。
- 一個(gè)進(jìn)程可以創(chuàng)建和銷毀多個(gè)線程,同一個(gè)進(jìn)程中的多個(gè)線程可以并發(fā)執(zhí)行
- 一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程
3.并發(fā)和并行
- 多線程程序在單核上運(yùn)行,就是并發(fā)
- 多個(gè)程程序在多核上運(yùn)行,就是并行
并發(fā):因?yàn)槭窃谝粋€(gè)CPU上,比如有10個(gè)線程,每個(gè)線程執(zhí)行10毫秒(進(jìn)行輪詢操作),從人的角度看,好像這10個(gè)線程都在運(yùn)行,但是從微觀上看,在某一個(gè)時(shí)間點(diǎn)看,其實(shí)只有一個(gè)線程在執(zhí)行,這就是并發(fā)。
并行:因?yàn)槭窃诙鄠€(gè)CPU上(比如有10個(gè)CPU),比如有10個(gè)線程,每個(gè)線程執(zhí)行10毫秒(各自在不同CPU上執(zhí)行),從人的角度看,這10個(gè)線程都在運(yùn)行,但是從微觀上看,在某一個(gè)時(shí)間點(diǎn)看,也同時(shí)有10個(gè)線程在執(zhí)行,這就是并行
4.Go協(xié)程和Go主線程
Go主線程(有程序員直接稱為線程/也可以理解成進(jìn)程):一個(gè)Go線程上,可以起多個(gè)攜程,你可以這樣理解,攜程是輕量的線程
Go協(xié)程的特點(diǎn)
有獨(dú)立的??臻g
共享程序堆空間
調(diào)度由用戶控制
攜程是輕量級(jí)的線程
案例說(shuō)明
請(qǐng)編寫一個(gè)程序,完成如下功能:
1.在主線程(可以理解成進(jìn)程)中,開啟一個(gè)goroutine,該攜程每隔1秒輸出“hello,world”
2.在主線程中也每隔一秒輸出“hello,golang”,輸出10次后,退出程序
3.要求主線程和goroutine同時(shí)執(zhí)行
4.畫出主線程和協(xié)程執(zhí)行流程圖
代碼實(shí)現(xiàn)
// 在主線程(可以理解成進(jìn)程)中,開啟一個(gè)goroutine,該協(xié)程每秒輸出 “hello,world” // 在主線程中也每隔一秒輸出“hello,golang”,輸出10次后,退出程序 // 要求主線程和goroutine同時(shí)執(zhí)行 //編寫一個(gè)函數(shù),每隔1秒輸出 “hello,world” func test(){ for i := 1;i<=10;i++{ fmt.Println("test() hello,world"+strconv.Itoa(i)) time.Sleep(time.Second) } } func main(){ go test() // 開啟了一個(gè)協(xié)程 for i:=1;i<=10;i++{ fmt.Println(" main() hello,golang"+strconv.Itoa(i)) time.Sleep(time.Second) } }
總結(jié)
- 主線程是一個(gè)物理線程,直接作用在CPU上的,是重量級(jí)的,非常耗費(fèi)CPU資源。
- 協(xié)程從主線程開啟的,是輕量級(jí)的線程,是邏輯態(tài)。對(duì)資源消耗相對(duì)少。
- Golang的協(xié)程機(jī)制是重要的特點(diǎn),可以輕松的開啟上萬(wàn)個(gè)協(xié)程。其他編程語(yǔ)言的并發(fā)機(jī)制是一般基于線程的,開啟過(guò)多的線程,資源耗費(fèi)大,這里就突顯Golang在并發(fā)上的優(yōu)勢(shì)了
MPG模式基本介紹
M:操作系統(tǒng)的主線程(是物理線程)
P:協(xié)程執(zhí)行需要的上下文
G:協(xié)程
5.設(shè)置Golang運(yùn)行的CPU數(shù)
介紹:為了充分利用多CPU的優(yōu)勢(shì),在Golang程序中設(shè)置運(yùn)行的CPU數(shù)目
package main import "fmt" import "runtime" func main(){ // 獲取當(dāng)前系統(tǒng)CPU的數(shù)量 num := runtime.NumCPU() // 這里設(shè)置num-1的CPU運(yùn)行g(shù)o程序 runtime.GOMAXPROCS(num) fmt.Println("num=",num) }
- go1.8后,默認(rèn)讓程序運(yùn)行在多個(gè)核上,可以不用設(shè)置了
- go1.8前,還是要設(shè)置一下,可以更高效的利用CPU
6.channel(管道)看需求
需求:現(xiàn)在要計(jì)算 1-200的各個(gè)數(shù)的階乘,并且把各個(gè)數(shù)的階乘放入到map中。最后顯示出來(lái)。要求使用goroutine完成
分析思路:
使用goroutine來(lái)完成,效率高,但是會(huì)出現(xiàn)并發(fā)/并行安全問(wèn)題
這里就提出了不同goroutine如何通信的問(wèn)題
代碼實(shí)現(xiàn)
使用goroutine來(lái)完成(看看使用gorotine并發(fā)完成會(huì)出現(xiàn)什么問(wèn)題?然后我們會(huì)去解決)
在運(yùn)行某個(gè)程序時(shí),如何知道是否存在資源競(jìng)爭(zhēng)問(wèn)題,方法很簡(jiǎn)單,在編譯該程序時(shí),增加一個(gè)參數(shù) -race即可
不同goroutine之間如何通訊
1.全局變量的互斥鎖
2.使用管道channel來(lái)解決
使用全局變量加鎖同步改進(jìn)程序
- 英文沒(méi)有對(duì)全局變量m加鎖,因此會(huì)出現(xiàn)資源爭(zhēng)奪問(wèn)題,代碼會(huì)出現(xiàn)錯(cuò)誤,提示concurrent map writes
- 解決方案:加入互斥鎖
- 我們的數(shù)的階乘很大,結(jié)果會(huì)越界,可以將求階乘改成sum += uint64(i)
源碼
package main import ( "fmt" "time" "sync" ) // 需求:現(xiàn)在要計(jì)算 1-200的各個(gè)數(shù)的階乘,并且把各個(gè)數(shù)的階乘放入到map中 // 最后顯示出來(lái)。要求使用goroutine完成 // 思路 // 1. 編寫一個(gè)函數(shù),來(lái)計(jì)算各個(gè)數(shù)的階乘,并放入到map中 // 2. 我們啟動(dòng)的協(xié)程多個(gè),統(tǒng)計(jì)的將結(jié)果放入到map中 // 3. map應(yīng)該做出一個(gè)全局的 var ( myMap = make(map[int]int,10) // 聲明一個(gè)全局的互斥鎖 // lock 是一個(gè)全局的互斥鎖 //sync 是包:synchornized 同步 // Mutex: 是互斥 lock sync.Mutex ) // test函數(shù)就是計(jì)算n!,讓將這個(gè)結(jié)果放入到myMap func test(n int){ res := 1 for i := 1;i<=n;i++{ res *= i } // 這里我們將res放入到myMap // 加鎖 lock.Lock() myMap[n] = res // concurrent map writes? // 解鎖 lock.Unlock() } func main(){ // 我們這里開啟多個(gè)協(xié)程完成這個(gè)任務(wù)[200個(gè)] for i := 1;i<=20;i++{ go test(i) } // 休眠10秒鐘【第二個(gè)問(wèn)題】 time.Sleep(time.Second * 10) lock.Lock() // 這里我們輸出結(jié)果 變量這個(gè)結(jié)果 for i,v := range myMap{ fmt.Printf("map[%d]=%d\n",i,v) } lock.Unlock() }
channel(管道)-基本使用
channel初始化
說(shuō)明:使用make進(jìn)行初始化
var intChan chan int
intChan = make(chan int,10)
向channel中寫入(存放)數(shù)據(jù)
var intChan chan int
intChan = make(chan int,10)
num := 999
intChan <-10
intChan <-num
管道的初始化,寫入數(shù)據(jù)到管道,從管道讀取數(shù)據(jù)及基本的注意事項(xiàng)
package main import ( "fmt" ) func main(){ // 演示一下管道的使用 // 1.創(chuàng)建一個(gè)可以存放3個(gè)int類型的管道 var intChan chan int intChan = make(chan int,3) // 2.看看intChannel是什么 fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n",intChan,&intChan) // 3.向管道寫入數(shù)據(jù) intChan<- 10 num := 211 intChan<- num // 注意點(diǎn),當(dāng)我們給管寫入數(shù)據(jù)時(shí),不能超過(guò)其容量 intChan<- 50 // intChan<- 98 //4. 看看管道的長(zhǎng)度和cap(容量) fmt.Printf("channel len=%v cap=%v \n",len(intChan),cap(intChan)) // 2,3 // 5.從管道中讀取數(shù)據(jù) var num2 int num2 = <-intChan fmt.Println("num2=",num2) fmt.Printf("channel len=%v cap=%v \n",len(intChan),cap(intChan)) // 2,3 // 6.在沒(méi)有使用協(xié)程的情況下,如果我們的管道數(shù)據(jù)已經(jīng)全部取出,再取就會(huì)報(bào)告 deadlock num3 := <-intChan num4 := <-intChan // num5 := <-intChan fmt.Println("num3=",num3,"num4=",num4)//,"num5=",num5) }
channel使用的注意事項(xiàng)
1.channel中只能存放指定的數(shù)據(jù)類型
2.channel的數(shù)據(jù)放滿后,就不能再放入了
3.如果從channel取出數(shù)據(jù)后,可以繼續(xù)放入
4. 在沒(méi)有使用協(xié)程的情況下,如果channel數(shù)據(jù)取完了,再取,就會(huì)報(bào)dead lock
示例代碼
package main import ( "fmt" ) type Cat struct{ Name string Age int } func main(){ // 定義一個(gè)存放任意數(shù)據(jù)類型的管道 3個(gè)數(shù)據(jù) // var callChan chan interface{} allChan := make(chan interface{},3) allChan<- 10 allChan<- "tom jack" cat := Cat{"小花貓",4} allChan<- cat // 我們希望獲得到管道中的第三個(gè)元素,則先將前2個(gè)推出 <-allChan <-allChan newCat := <-allChan // 從管道中取出的Cat是什么? fmt.Printf("newCat=%T,newCat=%v\n",newCat,newCat) // 下面的寫法是錯(cuò)誤的!編譯不通過(guò) // fmt.Printf("newCat.Name=%v",newCat.Name) // 使用類型斷言 a := newCat.(Cat) fmt.Printf("newCat.Name=%v",a.Name) }
channel的關(guān)閉
使用內(nèi)置函數(shù)close可以關(guān)閉channel,當(dāng)channel關(guān)閉后,就不能再向channel寫數(shù)據(jù)了,但是仍然可以從該channel讀取數(shù)據(jù)
channel的遍歷
channel支持for-range的方式進(jìn)行遍歷,請(qǐng)注意兩個(gè)細(xì)節(jié)
- 在遍歷時(shí),如果channel沒(méi)有關(guān)閉,則會(huì)出現(xiàn)deadlock的錯(cuò)誤
- 在遍歷時(shí),如果channel已經(jīng)關(guān)閉,則會(huì)正常遍歷數(shù)據(jù),遍歷完后,就會(huì)退出遍歷。
代碼演示:
package main import ( "fmt" ) func main(){ intChan := make(chan int,3) intChan<- 100 intChan<- 200 close(intChan) // close // 這是不能夠再寫入到channel // intChan<-300 fmt.Println("okook~") // 當(dāng)管道關(guān)閉后,讀取數(shù)據(jù)是可以的 n1 := <-intChan fmt.Println("n1=",n1) // 遍歷管道 intChan2 := make(chan int,100) for i := 0; i< 100;i++{ intChan2<-i*2 // 放入100個(gè)數(shù)據(jù)到管道 } // 遍歷管道不能使用普通的for循環(huán) // 在遍歷時(shí),如果channel沒(méi)有關(guān)閉,則會(huì)出現(xiàn)deadlock的錯(cuò)誤 // 在遍歷時(shí),如果channel已經(jīng)關(guān)閉,則會(huì)正常遍歷數(shù)據(jù),遍歷完后,就會(huì)退出遍歷 close(intChan2) for v := range intChan2{ fmt.Println("v=",v) } }
到此這篇關(guān)于Golang中g(shù)oroutine和channel使用介紹深入分析的文章就介紹到這了,更多相關(guān)Go goroutine與channel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go打印結(jié)構(gòu)體提升代碼調(diào)試效率實(shí)例詳解
這篇文章主要介紹了Go打印結(jié)構(gòu)體提升代碼調(diào)試效率實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02golang中package?is?not?in?GOROOT報(bào)錯(cuò)的真正解決辦法
這篇文章主要給大家介紹了關(guān)于golang中package?is?not?in?GOROOT報(bào)錯(cuò)的真正解決辦法,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)同樣遇到這個(gè)問(wèn)題的朋友具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-03-03淺談golang 中time.After釋放的問(wèn)題
這篇文章主要介紹了淺談golang 中time.After釋放的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05一文帶你了解Go語(yǔ)言中函數(shù)設(shè)計(jì)的實(shí)踐示例
良好設(shè)計(jì)的函數(shù)具有清晰的職責(zé)和邏輯結(jié)構(gòu),提供準(zhǔn)確的命名和適當(dāng)?shù)膮?shù)控制,下面我們將一一描述函數(shù)設(shè)計(jì)時(shí)能夠遵循的最佳實(shí)踐,希望對(duì)大家有所幫助2023-06-06Go語(yǔ)言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作
這篇文章主要介紹了Go語(yǔ)言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go結(jié)構(gòu)體從基礎(chǔ)到應(yīng)用深度探索
本文深入探討了結(jié)構(gòu)體的定義、類型、字面量表示和使用方法,旨在為讀者呈現(xiàn)Go結(jié)構(gòu)體的全面視角,通過(guò)結(jié)構(gòu)體,開發(fā)者可以實(shí)現(xiàn)更加模塊化、高效的代碼設(shè)計(jì),這篇文章旨在為您提供關(guān)于結(jié)構(gòu)體的深入理解,助您更好地利用Go語(yǔ)言的強(qiáng)大功能2023-10-10Go語(yǔ)言實(shí)現(xiàn)讀取文件的方式總結(jié)
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)讀取文件的幾個(gè)方式,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,感興趣的小伙伴可以收藏一下2023-04-04