Goroutine 和 Channel 講解
文章目錄
前言
Go 語言中並發程序可以用兩種方式來實現。
一種是 goroutine 和 channel,其支持 “交談循序程式”(communicating sequential processes)或被簡稱為 CSP。
CSP 是一個現代的並發程式模型,在這種程式模型中值會在不同的運行實例(goroutine)中傳遞,儘管大多數情況下被限制在單一實例中。
另一種是傳統的並發模型,多線程共享內存(基於共享變量的並發) , 會在後續單獨闡述。
goroutine
goroutine 是一個輕量級的線程 , 它與線程的區別是線程是操作系統中對於一個獨立運行實例的描述,不同的操作系統中,線程的實現也不盡相同;
對於 goroutine,操作系統並不知道它的存在 , goroutine 的調度是 Go 語言的運行時進行管理的。
啟動線程雖然比進程使用的資源少 , 但依然需要上下文切換等大量工作 , Go 語言有自己的調度器 , 許多 goroutine 的數據都是共享的 , 因此 goroutine 之間的切換會快很多 , 啟動 goroutine 所耗費的資源也很少。
| |
channel
channel 被稱為通道 , 是連接並發 goroutine 的管道 , 可以從一個 goroutine 向通道發送值 , 並在另一個 goroutine 中接收到這些值 。
每個 channel 都有一個特殊的類型 , 也就是 channel 可發送數據的類型 。 一個可以發送 int 類型數據的 channel 一般寫為 chan int。
一個 channel 有發送和接收兩個主要操作 , 都是通信行為 。
一個發送語句將一個值從一個 goroutine 通過 channel 發送到另一個執行接收操作的 goroutine 。
發送和接收兩個操作都是用 <- 運算符 。
在發送語句中 , <- 運算符分割 channel 和要發送的值 ; 在接收語句中 , <- 運算符寫在 channel 對象之前 , 一個不使用接收結果的接收操作也是合法的。
channel 的發送操作將導致發送者 goroutine 阻塞 , 直到另一個 goroutine 在相同的 channel 上執行接收操作 , 當發送的值通過 channel 成功傳輸之後 , 兩個 goroutine 可以繼續執行後面的語句 。
反之 , 如果接收操作先發生 , 那麼接收者 goroutine 也將阻塞 , 直到有另一個 goroutine 在相同的 Channels 上執行發送操作。
| |
channel 還支持 close 操作 , 用於關閉 channel , 隨後對基於該 channel 的任何發送操作都將導致 Panic 異常 。
對一個已經被 close 過的 channel 接收操作依然可以接受到之前已經成功發送的數據 ; 如果 channel 中已經沒有數據的話將產生一個零值(nil)的數據 。
| |
帶緩存的 channel
帶緩存的 channel 內部有一個元素隊列 。 隊列的最大容量是在調用 make 函數創建 channel 時通過第二個參數指定的 。
下面的語法創建了一個可以持有三個字符串元素的帶緩存 channel 。
| |
向緩存 channel 的發送操作就是向內部緩存隊列的尾部插入元素 , 接收操作則是從隊列的頭部刪除元素 。
如果內部緩存隊列是滿的 , 那麼發送操作將阻塞直到另一個 goroutine 執行接收操作而釋放了新的隊列空間。
相反,如果 channel 是空的 , 接收操作將阻塞直到有另一個 goroutine 執行發送操作而向隊列插入元素 。
| |
select
Go 語言的 select 的功能和 select、poll、epoll 相似 , 就是監聽 IO 操作 , 當 IO 操作發生時 , 觸發響應的動作 。
select 語法和 switch 相似 , 也會有幾個 case 和 default 分支 , 每一個 case 代表一個通信操作(在某個 channel 上進行發送或者接收)並且會包含一些語句組成一個語句塊。
| |
超時控制
select 是用來讓我們的程序監聽多個文件句柄的狀態變化的處理機制 。
當發起一些阻塞的請求後 , 可以用 select 機制輪詢掃描文件句柄 , 直到被監視的文件句柄有一個或多個發生了狀態改變。
channel 在系統層面來說也是個文件描述符 , 在 Go 語言中我們可以用 goroutine 並發執行任務 , 接著使用 select 來監視每個任務的 channel 情況 。
如果這幾個任務都長時間沒有回复 channel 信息 , 並且我們又有超時的需求 , 那麼我們可以使用一個 goroutine 來設置超時機制 , 具體做法就是啟動 sleep 並且在 sleep 之後回复 channel 信號。
| |