并行(parallel):指在同一時(shí)刻,有多條指令在多個(gè)處理器上同時(shí)執(zhí)行。
并發(fā)(concurrency):指在同一時(shí)刻只能有一條指令執(zhí)行,但多個(gè)進(jìn)程指令被快速的輪換執(zhí)行,使得在宏觀上具有多個(gè)進(jìn)程同時(shí)執(zhí)行的效果,但在微觀上并不是同時(shí)執(zhí)行的,只是把時(shí)間分成若干段,使多個(gè)進(jìn)程快速交替的執(zhí)行。
并行在多處理器系統(tǒng)中存在,而并發(fā)可以在單處理器和多處理器系統(tǒng)中都存在,并發(fā)能夠在單處理器系統(tǒng)中存在是因?yàn)椴l(fā)是并行的假象,并行要求程序能夠同時(shí)執(zhí)行多個(gè)操作,而并發(fā)只是要求程序假裝同時(shí)執(zhí)行多個(gè)操作(每個(gè)小時(shí)間片執(zhí)行一個(gè)操作,多個(gè)操作快速切換執(zhí)行)。?
創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比壺關(guān)網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式壺關(guān)網(wǎng)站制作公司更省心,省錢(qián),快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋壺關(guān)地區(qū)。費(fèi)用合理售后完善,十余年實(shí)體公司更值得信賴。
Coroutine(協(xié)程)是一種用戶態(tài)的輕量級(jí)線程,特點(diǎn)如下:
A、輕量級(jí)線程
B、非搶占式多任務(wù)處理,由協(xié)程主動(dòng)交出控制權(quán)。
C、編譯器/解釋器/虛擬機(jī)層面的任務(wù)
D、多個(gè)協(xié)程可能在一個(gè)或多個(gè)線程上運(yùn)行。
E、子程序是協(xié)程的一個(gè)特例。
不同語(yǔ)言對(duì)協(xié)程的支持:
A、C++通過(guò)Boost.Coroutine實(shí)現(xiàn)對(duì)協(xié)程的支持
B、Java不支持
C、Python通過(guò)yield關(guān)鍵字實(shí)現(xiàn)協(xié)程,Python3.5開(kāi)始使用async def對(duì)原生協(xié)程的支持
在Go語(yǔ)言中,只需要在函數(shù)調(diào)用前加上關(guān)鍵字go即可創(chuàng)建一個(gè)并發(fā)任務(wù)單元,新建的任務(wù)會(huì)被放入隊(duì)列中,等待調(diào)度器安排。
進(jìn)程在啟動(dòng)的時(shí)候,會(huì)創(chuàng)建一個(gè)主線程,主線程結(jié)束時(shí),程序進(jìn)程將終止,因此,進(jìn)程至少有一個(gè)線程。main函數(shù)里,必須讓主線程等待,確保進(jìn)程不會(huì)被終止。
go語(yǔ)言中并發(fā)指的是讓某個(gè)函數(shù)獨(dú)立于其它函數(shù)運(yùn)行的能力,一個(gè)goroutine是一個(gè)獨(dú)立的工作單元,Go的runtime(運(yùn)行時(shí))會(huì)在邏輯處理器上調(diào)度goroutine來(lái)運(yùn)行,一個(gè)邏輯處理器綁定一個(gè)操作系統(tǒng)線程,因此goroutine不是線程,是一個(gè)協(xié)程。
進(jìn)程:一個(gè)程序?qū)?yīng)一個(gè)獨(dú)立程序空間
線程:一個(gè)執(zhí)行空間,一個(gè)進(jìn)程可以有多個(gè)線程
邏輯處理器:執(zhí)行創(chuàng)建的goroutine,綁定一個(gè)線程
調(diào)度器:Go運(yùn)行時(shí)中的,分配goroutine給不同的邏輯處理器
全局運(yùn)行隊(duì)列:所有剛創(chuàng)建的goroutine隊(duì)列
本地運(yùn)行隊(duì)列:邏輯處理器的goroutine隊(duì)列
當(dāng)創(chuàng)建一個(gè)goroutine后,會(huì)先存放在全局運(yùn)行隊(duì)列中,等待Go運(yùn)行時(shí)的調(diào)度器進(jìn)行調(diào)度,把goroutine分配給其中的一個(gè)邏輯處理器,并放到邏輯處理器對(duì)應(yīng)的本地運(yùn)行隊(duì)列中,最終等著被邏輯處理器執(zhí)行即可。
Go的并發(fā)是管理、調(diào)度、執(zhí)行g(shù)oroutine的方式。
默認(rèn)情況下,Go默認(rèn)會(huì)給每個(gè)可用的物理處理器都分配一個(gè)邏輯處理器。
可以在程序開(kāi)頭使用runtime.GOMAXPROCS(n)設(shè)置邏輯處理器的數(shù)量。
如果需要設(shè)置邏輯處理器的數(shù)量,一般采用如下代碼設(shè)置:
runtime.GOMAXPROCS(runtime.NumCPU())
對(duì)于并發(fā),Go語(yǔ)言本身自己實(shí)現(xiàn)的調(diào)度,對(duì)于并行,與物理處理器的核數(shù)有關(guān),多核就可以并行并發(fā),單核只能并發(fā)。
在Go語(yǔ)言中,只需要在函數(shù)調(diào)用前加上關(guān)鍵字go即可創(chuàng)建一個(gè)并發(fā)任務(wù)單元,新建的任務(wù)會(huì)被放入隊(duì)列中,等待調(diào)度器安排。
package main
import (
"fmt"
"sync"
)
func main(){
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
fmt.Printf("Hello,Go.This is %d\n", i)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
fmt.Printf("Hello,World.This is %d\n", i)
}
}()
wg.Wait()
}
sync.WaitGroup是一個(gè)計(jì)數(shù)的信號(hào)量,使main函數(shù)所在主線程等待兩個(gè)goroutine執(zhí)行完成后再結(jié)束,否則兩個(gè)goroutine還在運(yùn)行時(shí),主線程已經(jīng)結(jié)束。
sync.WaitGroup使用非常簡(jiǎn)單,使用Add方法設(shè)設(shè)置計(jì)數(shù)器為2,每一個(gè)goroutine的函數(shù)執(zhí)行完后,調(diào)用Done方法減1。Wait方法表示如果計(jì)數(shù)器大于0,就會(huì)阻塞,main函數(shù)會(huì)一直等待2個(gè)goroutine完成再結(jié)束。
goroutine是輕量級(jí)的線程,占用的資源非常小(Go將每個(gè)goroutine stack的size默認(rèn)設(shè)置為2k)線程的切換由操作系統(tǒng)控制,而goroutine的切換則由用戶控制。
goroutinue本質(zhì)上是協(xié)程。
?goroutinue可以實(shí)現(xiàn)并行,即多個(gè)goroutinue可以在多個(gè)處理器同時(shí)運(yùn)行,而協(xié)程同一時(shí)刻只能在一個(gè)處理器上運(yùn)行。
goroutine之間的通信是通過(guò)channel,而協(xié)程的通信是通過(guò)yield和resume()操作。
高級(jí)語(yǔ)言對(duì)內(nèi)核線程的封裝實(shí)現(xiàn)通常有三種線程調(diào)度模型:
A、N:1模型。N個(gè)用戶空間線程在1個(gè)內(nèi)核空間線程上運(yùn)行,優(yōu)勢(shì)是上下文切換非??斓珶o(wú)法利用多核系統(tǒng)的優(yōu)點(diǎn)。
B、1:1模型。1個(gè)內(nèi)核空間線程運(yùn)行一個(gè)用戶空間線程,充分利用了多核系統(tǒng)的優(yōu)勢(shì)但上下文切換非常慢,因?yàn)槊恳淮握{(diào)度都會(huì)在用戶態(tài)和內(nèi)核態(tài)之間切換。
C、M:N模型。每個(gè)用戶線程對(duì)應(yīng)多個(gè)內(nèi)核空間線程,同時(shí)也可以一個(gè)內(nèi)核空間線程對(duì)應(yīng)多個(gè)用戶空間線程,使用任意個(gè)內(nèi)核模型管理任意個(gè)goroutine,但缺點(diǎn)是調(diào)度的復(fù)雜性。
Go的最小調(diào)度單元為goroutine,但操作系統(tǒng)最小的調(diào)度單元依然是線程,所以go調(diào)度器(go scheduler)要做的工作是如何將眾多的goroutine放在有限的線程上進(jìn)行高效而公平的調(diào)度。
操作系統(tǒng)的調(diào)度不失為高效和公平,比如CFS調(diào)度算法。go引入goroutine的核心原因是goroutine輕量級(jí),無(wú)論是從進(jìn)程到線程,還是從線程到goroutine,其核心都是為了使調(diào)度單元更加輕量級(jí),可以輕易創(chuàng)建幾萬(wàn)幾十萬(wàn)的goroutine而不用擔(dān)心內(nèi)存耗盡等問(wèn)題。go引入goroutine試圖在語(yǔ)言內(nèi)核層做到足夠高性能得同時(shí)(充分利用多核優(yōu)勢(shì)、使用epoll高效處理網(wǎng)絡(luò)/IO、實(shí)現(xiàn)垃圾回收等機(jī)制)盡量簡(jiǎn)化編程。
?Go 1.1開(kāi)始,Go scheduler實(shí)現(xiàn)了M:N的G-P-M線程調(diào)度模型,即任意數(shù)量的用戶態(tài)goroutine可以運(yùn)行在任意數(shù)量的內(nèi)核空間線程線程上,不僅可以使上線文切換更加輕量級(jí),又可以充分利用多核優(yōu)勢(shì)。
為了實(shí)現(xiàn)M:N線程調(diào)度機(jī)制,Go引入了3個(gè)結(jié)構(gòu)體:
M:操作系統(tǒng)的內(nèi)核空間線程
G:goroutine對(duì)象,G結(jié)構(gòu)體包含調(diào)度一個(gè)goroutine所需要的堆棧和instruction pointer(IP指令指針),以及其它一些重要的調(diào)度信息。每次go調(diào)用的時(shí)候,都會(huì)創(chuàng)建一個(gè)G對(duì)象。
P:Processor,調(diào)度的上下文,實(shí)現(xiàn)M:N調(diào)度模型的關(guān)鍵,M必須拿到P才能對(duì)G進(jìn)行調(diào)度,P限定了go調(diào)度goroutine的最大并發(fā)度。每一個(gè)運(yùn)行的M都必須綁定一個(gè)P。
P的個(gè)數(shù)是GOMAXPROCS(最大256),啟動(dòng)時(shí)固定,一般不修改;?M的個(gè)數(shù)和P的個(gè)數(shù)不一定相同(會(huì)有休眠的M或者不需要太多的M);每一個(gè)P保存著本地G任務(wù)隊(duì)列,也能使用全局G任務(wù)隊(duì)列。
全局G任務(wù)隊(duì)列會(huì)和各個(gè)本地G任務(wù)隊(duì)列按照一定的策略互相交換。
P是用一個(gè)全局?jǐn)?shù)組(255)來(lái)保存的,并且維護(hù)著一個(gè)全局的P空閑鏈表。
每次調(diào)用go的時(shí)候,都會(huì):
A、創(chuàng)建一個(gè)G對(duì)象,加入到本地隊(duì)列或者全局隊(duì)列
B、如果有空閑的P,則創(chuàng)建一個(gè)M
C、M會(huì)啟動(dòng)一個(gè)底層線程,循環(huán)執(zhí)行能找到的G任務(wù)
D、G任務(wù)的執(zhí)行順序是先從本地隊(duì)列找,本地沒(méi)有則從全局隊(duì)列找(一次性轉(zhuǎn)移(全局G個(gè)數(shù)/P個(gè)數(shù))個(gè),再去其它P中找(一次性轉(zhuǎn)移一半)。
E、G任務(wù)執(zhí)行是按照隊(duì)列順序(即調(diào)用go的順序)執(zhí)行的。
創(chuàng)建一個(gè)M過(guò)程如下:
A、先找到一個(gè)空閑的P,如果沒(méi)有則直接返回。
B、調(diào)用系統(tǒng)API創(chuàng)建線程,不同的操作系統(tǒng)調(diào)用方法不一樣。
C、?在創(chuàng)建的線程里循環(huán)執(zhí)行G任務(wù)
如果一個(gè)系統(tǒng)調(diào)用或者G任務(wù)執(zhí)行太長(zhǎng),會(huì)一直占用內(nèi)核空間線程,由于本地隊(duì)列的G任務(wù)是順序執(zhí)行的,其它G任務(wù)就會(huì)阻塞。因此,Go程序啟動(dòng)的時(shí)候,會(huì)專門(mén)創(chuàng)建一個(gè)線程sysmon,用來(lái)監(jiān)控和管理,sysmon內(nèi)部是一個(gè)循環(huán):
A、記錄所有P的G任務(wù)計(jì)數(shù)schedtick,schedtick會(huì)在每執(zhí)行一個(gè)G任務(wù)后遞增。
B、如果檢查到?schedtick一直沒(méi)有遞增,說(shuō)明P一直在執(zhí)行同一個(gè)G任務(wù),如果超過(guò)一定的時(shí)間(10ms),在G任務(wù)的棧信息里面加一個(gè)標(biāo)記。
C、G任務(wù)在執(zhí)行的時(shí)候,如果遇到非內(nèi)聯(lián)函數(shù)調(diào)用,就會(huì)檢查一次標(biāo)記,然后中斷自己,把自己加到隊(duì)列末尾,執(zhí)行下一個(gè)G。
D、如果沒(méi)有遇到非內(nèi)聯(lián)函數(shù)(有時(shí)候正常的小函數(shù)會(huì)被優(yōu)化成內(nèi)聯(lián)函數(shù))調(diào)用,會(huì)一直執(zhí)行G任務(wù),直到goroutine自己結(jié)束;如果goroutine是死循環(huán),并且GOMAXPROCS=1,阻塞。
Go沒(méi)有時(shí)間片的概念。如果某個(gè)G沒(méi)有進(jìn)行system call調(diào)用、沒(méi)有進(jìn)行I/O操作、沒(méi)有阻塞在一個(gè)channel操作上,M通過(guò)搶占式調(diào)度讓長(zhǎng)任務(wù)G停下來(lái)并調(diào)度下一個(gè)G。
除非極端的無(wú)限循環(huán)或死循環(huán),否則只要G調(diào)用函數(shù),Go runtime就有搶占G的機(jī)會(huì)。Go程序啟動(dòng)時(shí),Go runtime會(huì)啟動(dòng)一個(gè)名為sysmon的M(一般稱為監(jiān)控線程),sysmon無(wú)需綁定P即可運(yùn)行。sysmon是GO程序啟動(dòng)時(shí)創(chuàng)建的一個(gè)用于監(jiān)控管理的線程。
sysmon每20us~10ms啟動(dòng)一次,sysmon主要完成如下工作:
A、釋放閑置超過(guò)5分鐘的span物理內(nèi)存;
B、如果超過(guò)2分鐘沒(méi)有垃圾回收,強(qiáng)制執(zhí)行;
C、將長(zhǎng)時(shí)間未處理的netpoll結(jié)果添加到任務(wù)隊(duì)列;
D、向長(zhǎng)時(shí)間運(yùn)行的G任務(wù)發(fā)出搶占調(diào)度;
E、收回因syscall長(zhǎng)時(shí)間阻塞的P;
如果一個(gè)G任務(wù)運(yùn)行10ms,sysmon就會(huì)認(rèn)為其運(yùn)行時(shí)間太久而發(fā)出搶占式調(diào)度的請(qǐng)求。一旦G的搶占標(biāo)志位被設(shè)為true,那么待G下一次調(diào)用函數(shù)或方法時(shí),runtime便可以將G搶占,并移出運(yùn)行狀態(tài),放入P的local runq中,等待下一次被調(diào)度。
runtime.Gosched()用于讓出CPU時(shí)間片,讓出當(dāng)前goroutine的執(zhí)行權(quán)限,調(diào)度器安排其它等待的任務(wù)運(yùn)行,并在下次某個(gè)時(shí)候從該位置恢復(fù)執(zhí)行。
調(diào)用runtime.Goexit()將立即終止當(dāng)前goroutine執(zhí)?,調(diào)度器確保所有已注冊(cè)defer延遲調(diào)用被執(zhí)行。
調(diào)用runtime.GOMAXPROCS()用來(lái)設(shè)置可以并行計(jì)算的CPU核數(shù)的最大值,并返回設(shè)置前的值。
Channel是goroutine之間通信的通道,用于goroutine之間發(fā)消息和接收消息。Channel是一種引用類型的數(shù)據(jù),可以作為參數(shù),也可以作為返回值。
channel聲明使用chan關(guān)鍵字,channel的創(chuàng)建需要指定通道中發(fā)送和接收數(shù)據(jù)的類型。
使用make來(lái)建立一個(gè)信道:
var channel chan int = make(chan int)
// 或channel := make(chan int)
make有第二個(gè)參數(shù),用于指定通道的大小。
//發(fā)送數(shù)據(jù):寫(xiě)
channel<- data
//接收數(shù)據(jù):讀
data := <- channel
關(guān)閉通道:發(fā)送方關(guān)閉通道,用于通知接收方已經(jīng)沒(méi)有數(shù)據(jù)
關(guān)閉通道后,其它goroutine訪問(wèn)通道獲取數(shù)據(jù)時(shí),得到零值和false
有條件結(jié)束死循環(huán):
for{
v ,ok := <- chan
if ok== false{
//通道已經(jīng)關(guān)閉。。
break
}
}
//循環(huán)從通道中獲取數(shù)據(jù),直到通道關(guān)閉。
for v := range channel{
//從通道讀取數(shù)據(jù)
}
Channel使用示例如下:
package main
import (
"fmt"
"time"
)
type Person struct {
name string
age uint8
address Address
}
type Address struct {
city string
district string
}
func SendMessage(person *Person, channel chan Person){
go func(person *Person, channel chan Person) {
fmt.Printf("%s send a message.\n", person.name)
channel<-*person
for i := 0; i < 5; i++ {
channel<- *person
}
close(channel)
fmt.Println("channel is closed.")
}(person, channel)
}
func main() {
channel := make(chan Person,1)
harry := Person{
"Harry",
30,
Address{"London","Oxford"},
}
go SendMessage(&harry, channel)
data := <-channel
fmt.Printf("main goroutine receive a message from %s.\n", data.name)
for {
i, ok := <-channel
time.Sleep(time.Second)
if !ok {
fmt.Println("channel is empty.")
break
}else{
fmt.Printf("receive %s\n",i.name)
}
}
}
結(jié)果如下:
Harry send a message.
main goroutine receive a message from Harry.
receive Harry
receive Harry
receive Harry
channel is closed.
receive Harry
receive Harry
channel is empty.
Go運(yùn)行時(shí)系統(tǒng)并沒(méi)有在通道channel被關(guān)閉后立即把false作為相應(yīng)接收操作的第二個(gè)結(jié)果,而是等到接收端把已在通道中的所有元素值都接收到后才這樣做,確保在發(fā)送端關(guān)閉通道的安全性。
被關(guān)閉的通道會(huì)禁止數(shù)據(jù)流入, 是只讀的,仍然可以從關(guān)閉的通道中取出數(shù)據(jù),但不能再寫(xiě)入數(shù)據(jù)。
給一個(gè)nil的channel發(fā)送數(shù)據(jù),造成永遠(yuǎn)阻塞?;從一個(gè)nil的channel接收數(shù)據(jù),造成永遠(yuǎn)阻塞。給一個(gè)已經(jīng)關(guān)閉的channel發(fā)送數(shù)據(jù),引起panic?;
從一個(gè)已經(jīng)關(guān)閉的channel接收數(shù)據(jù),返回帶緩存channel中緩存的值,如果通道中無(wú)緩存,返回0。
make創(chuàng)建通道時(shí),默認(rèn)沒(méi)有第二個(gè)參數(shù),通道的大小為0,稱為無(wú)緩沖通道。
無(wú)緩沖的通道是指通道的大小為0,即通道在接收前沒(méi)有能力保存任何值,無(wú)緩沖通道發(fā)送goroutine和接收gouroutine必須是同步的,如果沒(méi)有同時(shí)準(zhǔn)備好,先執(zhí)行的操作就會(huì)阻塞等待,直到另一個(gè)相對(duì)應(yīng)的操作準(zhǔn)備好為止。無(wú)緩沖通道也稱為同步通道。
無(wú)緩沖的信道永遠(yuǎn)不會(huì)存儲(chǔ)數(shù)據(jù),只負(fù)責(zé)數(shù)據(jù)的流通。從無(wú)緩沖信道取數(shù)據(jù),必須要有數(shù)據(jù)流進(jìn)來(lái)才可以,否則當(dāng)前goroutine會(huì)阻塞;數(shù)據(jù)流入無(wú)緩沖信道, 如果沒(méi)有其它goroutine來(lái)拿取走數(shù)據(jù),那么當(dāng)前goroutine會(huì)阻塞。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
var sum int = 0
for i := 0; i < 10; i++ {
sum += i
}
//發(fā)送數(shù)據(jù)到通道
ch <- sum
}()
//從通道接收數(shù)據(jù)
fmt.Println(<-ch)
}
在計(jì)算sum和的goroutine沒(méi)有執(zhí)行完,將值賦發(fā)送到ch通道前,fmt.Println(<-ch)會(huì)一直阻塞等待,main函數(shù)所在的主goroutine就不會(huì)終止,只有當(dāng)計(jì)算和的goroutine完成后,并且發(fā)送到ch通道的操作準(zhǔn)備好后,main函數(shù)的<-ch會(huì)接收計(jì)算好的值,然后打印出來(lái)。
無(wú)緩存通道的發(fā)送數(shù)據(jù)和讀取數(shù)據(jù)的操作不能放在同一個(gè)協(xié)程中,防止發(fā)生死鎖。通常,先創(chuàng)建一個(gè)goroutine對(duì)通道進(jìn)行操作,此時(shí)新創(chuàng)建goroutine會(huì)阻塞,然后再在主goroutine中進(jìn)行通道的反向操作,實(shí)現(xiàn)goroutine解鎖,即必須goroutine在前,解鎖goroutine在后。
make創(chuàng)建通道時(shí),指定通道的大小時(shí),稱為有緩沖通道。
對(duì)于帶緩存通道,只要通道中緩存不滿,可以一直向通道中發(fā)送數(shù)據(jù),直到緩存已滿;同理只要通道中緩存不為0,可以一直從通道中讀取數(shù)據(jù),直到通道的緩存變?yōu)椋安艜?huì)阻塞。
相對(duì)于不帶緩存通道,帶緩存通道不易造成死鎖,可以同時(shí)在一個(gè)goroutine中放心使用。
帶緩存通道不僅可以流通數(shù)據(jù),還可以緩存數(shù)據(jù),當(dāng)帶緩存通道達(dá)到滿的狀態(tài)的時(shí)候才會(huì)阻塞,此時(shí)帶緩存通道不能再承載更多的數(shù)據(jù)。
帶緩存通道是先進(jìn)先出的。
對(duì)于某些特殊的場(chǎng)景,需要限制一個(gè)通道只可以接收,不能發(fā)送;限制一個(gè)通道只能發(fā)送,不能接收。只能單向接收或發(fā)送的通道稱為單向通道。
定義單向通道只需要在定義的時(shí)候,帶上<-即可。
var send chan<- int //只能發(fā)送
var receive <-chan int //只能接收
<-操作符的位置在后面只能發(fā)送,對(duì)應(yīng)發(fā)送操作;<-操作符的位置在前面只能接收,對(duì)應(yīng)接收操作。
單向通道通常用于函數(shù)或者方法的參數(shù)。
當(dāng)一個(gè)通道關(guān)閉時(shí), 所有對(duì)此通道的讀取的goroutine都會(huì)退出阻塞。
package main
import (
"fmt"
"time"
)
func notify(id int, channel chan int){
<-channel//接收到數(shù)據(jù)或通道關(guān)閉時(shí)退出阻塞
fmt.Printf("%d receive a message.\n", id)
}
func broadcast(channel chan int){
fmt.Printf("Broadcast:\n")
close(channel)//關(guān)閉通道
}
func main(){
channel := make(chan int,1)
for i:=0;i<10 ;i++ {
go notify(i,channel)
}
go broadcast(channel)
time.Sleep(time.Second)
}
select用于在多個(gè)channel上同時(shí)進(jìn)行偵聽(tīng)并收發(fā)消息,當(dāng)任何一個(gè)case滿足條件時(shí)即執(zhí)行,如果沒(méi)有可執(zhí)行的case則會(huì)執(zhí)行default的case,如果沒(méi)有指定default case,則會(huì)阻塞程序。select的語(yǔ)法如下:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/*可以定義任意數(shù)量的 case */
default : /*可選 */
statement(s);
}
Select多路復(fù)用中:
A、每個(gè)case都必須是一次通信
B、所有channel表達(dá)式都會(huì)被求值
C、所有被發(fā)送的表達(dá)式都會(huì)被求值
D、如果任意某個(gè)通信可以進(jìn)行,它就執(zhí)行;其它被忽略。
E、如果有多個(gè)case都可以運(yùn)行,Select會(huì)隨機(jī)公平地選出一個(gè)執(zhí)行。其它不會(huì)執(zhí)行。
F、否則,如果有default子句,則執(zhí)行default語(yǔ)句。如果沒(méi)有default子句,select將阻塞,直到某個(gè)通信可以運(yùn)行;Go不會(huì)重新對(duì)channel或值進(jìn)行求值。
package main
import (
"fmt"
"time"
)
func doWork(channels *[10]chan int){
for {
select {
case x1 := <-channels[0]:
fmt.Println("receive x1: ",x1)
case x2 := <-channels[1]:
fmt.Println("receive x2: ",x2)
case x3 := <-channels[2]:
fmt.Println("receive x3: ",x3)
case x4 := <-channels[3]:
fmt.Println("receive x4: ",x4)
case x5 := <-channels[4]:
fmt.Println("receive x5: ",x5)
case x6 := <-channels[5]:
fmt.Println("receive x6: ",x6)
case x7 := <-channels[6]:
fmt.Println("receive x7: ",x7)
case x8 := <-channels[7]:
fmt.Println("receive x8: ",x8)
case x9 := <-channels[8]:
fmt.Println("receive x9: ",x9)
case x10 := <-channels[9]:
fmt.Println("receive x10: ",x10)
}
}
}
func main(){
var channels [10]chan int
go doWork(&channels)
for i := 0; i < 10; i++ {
channels[i] = make(chan int,1)
channels[i]<- i
}
time.Sleep(time.Second*5)
}
結(jié)果如下:
receive x4: 3
receive x10: 9
receive x9: 8
receive x5: 4
receive x2: 1
receive x7: 6
receive x8: 7
receive x1: 0
receive x3: 2
receive x6: 5
Go程序中死鎖是指所有的goroutine在等待資源的釋放。
通常,死鎖的報(bào)錯(cuò)信息如下:fatal error: all goroutines are asleep - deadlock!
Goroutine死鎖產(chǎn)生的原因如下:
A、只在單一的goroutine里操作無(wú)緩沖信道,一定死鎖
B、非緩沖信道上如果發(fā)生流入無(wú)流出,或者流出無(wú)流入,會(huì)導(dǎo)致死鎖
因此,解決死鎖的方法有:
A、取走無(wú)緩沖通道的數(shù)據(jù)或是發(fā)送數(shù)據(jù)到無(wú)緩沖通道
B、使用緩沖通道