溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go語言開發(fā)(九)、Go語言并發(fā)編程

發(fā)布時(shí)間:2020-07-08 05:46:11 來源:網(wǎng)絡(luò) 閱讀:28418 作者:天山老妖S 欄目:編程語言

Go語言開發(fā)(九)、Go語言并發(fā)編程

一、goroutine簡(jiǎn)介

1、并發(fā)與并行簡(jiǎn)介

并行(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í)行)。?

2、Coroutine簡(jiǎn)介

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è)特例。
不同語言對(duì)協(xié)程的支持:
A、C++通過Boost.Coroutine實(shí)現(xiàn)對(duì)協(xié)程的支持
B、Java不支持
C、Python通過yield關(guān)鍵字實(shí)現(xiàn)協(xié)程,Python3.5開始使用async def對(duì)原生協(xié)程的支持

3、goroutine簡(jiǎn)介

在Go語言中,只需要在函數(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語言中并發(fā)指的是讓某個(gè)函數(shù)獨(dú)立于其它函數(shù)運(yùn)行的能力,一個(gè)goroutine是一個(gè)獨(dú)立的工作單元,Go的runtime(運(yùn)行時(shí))會(huì)在邏輯處理器上調(diào)度goroutine來運(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è)邏輯處理器。
可以在程序開頭使用runtime.GOMAXPROCS(n)設(shè)置邏輯處理器的數(shù)量。
如果需要設(shè)置邏輯處理器的數(shù)量,一般采用如下代碼設(shè)置:
runtime.GOMAXPROCS(runtime.NumCPU())
對(duì)于并發(fā),Go語言本身自己實(shí)現(xiàn)的調(diào)度,對(duì)于并行,與物理處理器的核數(shù)有關(guān),多核就可以并行并發(fā),單核只能并發(fā)。

4、goroutinue使用示例

在Go語言中,只需要在函數(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é)束。

5、goroutine的本質(zhì)

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之間的通信是通過channel,而協(xié)程的通信是通過yield和resume()操作。

二、goroutine調(diào)度機(jī)制

1、線程調(diào)度模型

高級(jí)語言對(duì)內(nèi)核線程的封裝實(shí)現(xiàn)通常有三種線程調(diào)度模型:
A、N:1模型。N個(gè)用戶空間線程在1個(gè)內(nèi)核空間線程上運(yùn)行,優(yōu)勢(shì)是上下文切換非常快但無法利用多核系統(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ù)雜性。

2、Go調(diào)度器簡(jiǎn)介

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í),無論是從進(jìn)程到線程,還是從線程到goroutine,其核心都是為了使調(diào)度單元更加輕量級(jí),可以輕易創(chuàng)建幾萬幾十萬的goroutine而不用擔(dān)心內(nèi)存耗盡等問題。go引入goroutine試圖在語言內(nèi)核層做到足夠高性能得同時(shí)(充分利用多核優(yōu)勢(shì)、使用epoll高效處理網(wǎng)絡(luò)/IO、實(shí)現(xiàn)垃圾回收等機(jī)制)盡量簡(jiǎn)化編程。

3、Go調(diào)度器實(shí)現(xiàn)原理

?Go 1.1開始,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ì)。
Go語言開發(fā)(九)、Go語言并發(fā)編程
為了實(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ì)列。
Go語言開發(fā)(九)、Go語言并發(fā)編程
全局G任務(wù)隊(duì)列會(huì)和各個(gè)本地G任務(wù)隊(duì)列按照一定的策略互相交換。
P是用一個(gè)全局?jǐn)?shù)組(255)來保存的,并且維護(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ì)列找,本地沒有則從全局隊(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過程如下:
A、先找到一個(gè)空閑的P,如果沒有則直接返回。
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ì)專門創(chuàng)建一個(gè)線程sysmon,用來監(jiān)控和管理,sysmon內(nèi)部是一個(gè)循環(huán):
A、記錄所有P的G任務(wù)計(jì)數(shù)schedtick,schedtick會(huì)在每執(zhí)行一個(gè)G任務(wù)后遞增。
B、如果檢查到?schedtick一直沒有遞增,說明P一直在執(zhí)行同一個(gè)G任務(wù),如果超過一定的時(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、如果沒有遇到非內(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,阻塞。

4、搶占式調(diào)度

Go沒有時(shí)間片的概念。如果某個(gè)G沒有進(jìn)行system call調(diào)用、沒有進(jìn)行I/O操作、沒有阻塞在一個(gè)channel操作上,M通過搶占式調(diào)度讓長(zhǎng)任務(wù)G停下來并調(diào)度下一個(gè)G。
除非極端的無限循環(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無需綁定P即可運(yùn)行。sysmon是GO程序啟動(dòng)時(shí)創(chuàng)建的一個(gè)用于監(jiān)控管理的線程。
sysmon每20us~10ms啟動(dòng)一次,sysmon主要完成如下工作:
A、釋放閑置超過5分鐘的span物理內(nèi)存;
B、如果超過2分鐘沒有垃圾回收,強(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包

1、Gosched

runtime.Gosched()用于讓出CPU時(shí)間片,讓出當(dāng)前goroutine的執(zhí)行權(quán)限,調(diào)度器安排其它等待的任務(wù)運(yùn)行,并在下次某個(gè)時(shí)候從該位置恢復(fù)執(zhí)行。

2、Goexit

調(diào)用runtime.Goexit()將立即終止當(dāng)前goroutine執(zhí)?,調(diào)度器確保所有已注冊(cè)defer延遲調(diào)用被執(zhí)行。

3、GOMAXPROCS

調(diào)用runtime.GOMAXPROCS()用來設(shè)置可以并行計(jì)算的CPU核數(shù)的最大值,并返回設(shè)置前的值。

四、Channel通道

1、Channel簡(jiǎn)介

Channel是goroutine之間通信的通道,用于goroutine之間發(fā)消息和接收消息。Channel是一種引用類型的數(shù)據(jù),可以作為參數(shù),也可以作為返回值。

2、Channel的創(chuàng)建

channel聲明使用chan關(guān)鍵字,channel的創(chuàng)建需要指定通道中發(fā)送和接收數(shù)據(jù)的類型。
使用make來建立一個(gè)信道:

var channel chan int = make(chan int)
// 或channel := make(chan int)

make有第二個(gè)參數(shù),用于指定通道的大小。

3、Channel的操作

//發(fā)送數(shù)據(jù):寫
channel<- data
//接收數(shù)據(jù):讀
data := <- channel

關(guān)閉通道:發(fā)送方關(guān)閉通道,用于通知接收方已經(jīng)沒有數(shù)據(jù)
關(guān)閉通道后,其它goroutine訪問通道獲取數(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)并沒有在通道channel被關(guān)閉后立即把false作為相應(yīng)接收操作的第二個(gè)結(jié)果,而是等到接收端把已在通道中的所有元素值都接收到后才這樣做,確保在發(fā)送端關(guān)閉通道的安全性。
被關(guān)閉的通道會(huì)禁止數(shù)據(jù)流入, 是只讀的,仍然可以從關(guān)閉的通道中取出數(shù)據(jù),但不能再寫入數(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中緩存的值,如果通道中無緩存,返回0。

4、無緩沖通道

make創(chuàng)建通道時(shí),默認(rèn)沒有第二個(gè)參數(shù),通道的大小為0,稱為無緩沖通道。
無緩沖的通道是指通道的大小為0,即通道在接收前沒有能力保存任何值,無緩沖通道發(fā)送goroutine和接收gouroutine必須是同步的,如果沒有同時(shí)準(zhǔn)備好,先執(zhí)行的操作就會(huì)阻塞等待,直到另一個(gè)相對(duì)應(yīng)的操作準(zhǔn)備好為止。無緩沖通道也稱為同步通道。
無緩沖的信道永遠(yuǎn)不會(huì)存儲(chǔ)數(shù)據(jù),只負(fù)責(zé)數(shù)據(jù)的流通。從無緩沖信道取數(shù)據(jù),必須要有數(shù)據(jù)流進(jìn)來才可以,否則當(dāng)前goroutine會(huì)阻塞;數(shù)據(jù)流入無緩沖信道, 如果沒有其它goroutine來拿取走數(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沒有執(zhí)行完,將值賦發(fā)送到ch通道前,fmt.Println(<-ch)會(huì)一直阻塞等待,main函數(shù)所在的主goroutine就不會(huì)終止,只有當(dāng)計(jì)算和的goroutine完成后,并且發(fā)送到ch通道的操作準(zhǔn)備好后,main函數(shù)的<-ch會(huì)接收計(jì)算好的值,然后打印出來。
無緩存通道的發(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在后。

5、有緩沖通道

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)先出的。

6、單向通道

對(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ù)。

五、channel應(yīng)用

1、廣播功能實(shí)現(xiàn)

當(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)
}

2、select使用

select用于在多個(gè)channel上同時(shí)進(jìn)行偵聽并收發(fā)消息,當(dāng)任何一個(gè)case滿足條件時(shí)即執(zhí)行,如果沒有可執(zhí)行的case則會(huì)執(zhí)行default的case,如果沒有指定default case,則會(huì)阻塞程序。select的語法如下:

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語句。如果沒有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里操作無緩沖信道,一定死鎖
B、非緩沖信道上如果發(fā)生流入無流出,或者流出無流入,會(huì)導(dǎo)致死鎖
因此,解決死鎖的方法有:
A、取走無緩沖通道的數(shù)據(jù)或是發(fā)送數(shù)據(jù)到無緩沖通道
B、使用緩沖通道

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI