昨天講到Goroutine有稍微簡單介紹Channel,Channel是Go語言中極為重要的併發通訊機制,它就像是不同goroutines之間的話筒,允許它們安全地傳遞資料和信息。這個強大的工具使得Go語言在處理併發任務時非常優雅和高效。通過Channel,可以協調goroutines的操作,防止競爭條件,並實現高效的併發程式設計。
Channel是Go語言中一個強大的併發原語言功能,用於在不同的goroutine之間傳遞資料。它們提供了一種通訊的機制,可以讓goroutines之間安全地交換信息,而不需要額外的互斥鎖或信號量。
可以把channel比喻成傳紙條,傳紙條可以是單向,也可以是雙向,單向可以想像類似以前傳紙條的中間人,他只接收和傳遞,紙條也有大小的限制,寫滿了就不能再繼續寫,並且需要等待對方將紙條進行回覆後你才能繼續。
在Go中,可以使用內建的make
函數來建立一個新的Channel。以下是使用 make
函數建立整數類型的Channel
ch := make(chan int)
建立完後,就可以將資料發送到Channel中,然後在其他goroutine中接收它們。以下是一個簡單的發送和接收資料的例子:
func main() {
ch := make(chan int)
go func() {
ch <- 42 // 發送資料到Channel
}()
value := <-ch // 從Channel接收資料
fmt.Println(value) // 輸出:42
這個例子中,我們建立了一個goroutine,它將數字42發送到Channel,然後主goroutine,也就是main函數,接收到這個數字。
紙條用完會丟掉,Channel用完也是要關掉,您可能需要關閉Channel,以通知接收方不再有新的資料可用。要關閉Channel,可以使用close
函數,如下所示:
close(ch)
接收方可以使用特殊的語法來檢查Channel是否已關閉:
value, ok := <-ch
if !ok {
// Channel已經關閉
}
這可以防止接收方在Channel已關閉的情況下繼續等待。 並且已經關閉的Channel依然可以進行接收的操作,但不能再進行發送。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1 * time.Second)
ch <- i // 向Channel發送資料
}
close(ch) // 關閉Channel
}()
// 接收資料直到Channel被關閉
for val := range ch {
fmt.Println(val)
}
}
範例中首先創建了一個整數型的Channel ch
,並在一個獨立的goroutine中向該Channel發送一系列數字。一旦所有資料都被發送完畢,我們通過close(ch)
關閉了Channel。
主goroutine使用for num := range ch
的方式來從Channel中接收資料,這是一種常見的接收方式。當Channel被關閉後,range
迴圈會自動退出,從而避免了無限等待。
傳紙條有方向,Channel肯定也有方向,Channel的方向主要用來限制操作方向,增加程式的安全性。Channel可以是雙向的(可以用來發送和接收),也可以是單向的(只能用來發送或接收)。
ch := make(chan T)
,其中T
是所需的資料類型。ch := make(chan int)
go func() {
ch <- 42 // 向Channel發送資料
fmt.Println(<-ch) // 從Channel接收資料
}()
單向Channel只允許發送或接收操作,以增加程式的安全性和可讀性。它們可以分為兩種類型:
ch := make(chan<- T)
,其中T
是所需的資料類型。func sendData(ch chan<- int, data int) {
ch <- data
}
func main() {
ch := make(chan<- int)
go sendData(ch, 42) // 向單向發送Channel發送資料
// 這行程式碼無法編譯,因為我們無法從單向發送Channel接收資料
value := <-ch // 編譯錯誤
}
ch := make(<-chan T)
,其中T
是所需的資料類型。func receiveData(ch <-chan int) {
data := <-ch
fmt.Println(data)
}
func main() {
ch := make(<-chan int)
go receiveData(ch) // 從單向接收Channel接收資料
// 這行程式碼無法編譯,因為我們無法向單向接收Channel發送資料
ch <- 42 // 編譯錯誤
}
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
// 启动一个goroutine发送消息到Channel
go func() {
ch <- "Hello from goroutine!"
}()
// 主goroutine接收消息
msg := <-ch
fmt.Println(msg) // 输出:Hello from goroutine!
}
在這個例子中,我們建立了一個String類型的Channel ch
,然後在一個goroutine中將消息發送到該Channel,主goroutine接收並打印消息。
2. 阻塞操作:Channel的發送(send)和接收(receive)操作都是阻塞的。如果發送方沒有goroutine準備好接收資料,或者接收方沒有資料可用,操作將被阻塞,直到滿足條件為止。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
time.Sleep(2 * time.Second)
ch <- 42 // 將資料42發送到Channel
}()
select {
case value := <-ch:
fmt.Printf("接收到資料:%d\n", value)
case <-time.After(1 * time.Second):
fmt.Println("超時:未能接收到資料")
}
}
在範例中,我們先建立了一個Channel ch
,然後啟動一個goroutine,該goroutine將在2秒後向Channel發送數字42。接著,我們使用select
語句來等待Channel的資料接收操作,但我們設置了一個1秒的超時。
這裡可能發生以下情況:
case value := <-ch
分支處接收到資料,並輸出「接收到資料:42」。case <-time.After(1 * time.Second)
分支,並輸出「超時:未能接收到資料」。這個範例展示了Channel的阻塞操作特性,無論是成功接收資料還是超時,都取決於Channel中是否有可用的資料以及操作是否阻塞。這種阻塞操作使得在併發程式設計中能夠安全地等待資料的到來,而不需要使用額外的等待或輪詢機制。
3. 具有方向性:Channel可以是單向或雙向的。單向Channel只允許發送或接收操作,而雙向Channel則沒有這限制。
4. 緩衝Channel:可以使用make
函數的第二個參數來建立緩衝Channel。緩衝Channel允許一定數量的資料在不阻塞的情況下進行發送,直到緩衝區滿為止。
package main
import "fmt"
func main() {
ch := make(chan int, 2) // 建立一個容量為2的緩衝Channel
ch <- 1 // 發送第一個數字,不會阻塞
ch <- 2 // 發送第二個數字,不會阻塞
// ch <- 3 // 如果再嘗試發送第三個數字,將會阻塞,因為緩衝區已滿
fmt.Println(<-ch) // 從Channel接收第一個數字
fmt.Println(<-ch) // 從Channel接收第二個數字
}
在這個範例中,我們建立了一個容量為2的緩衝Channel。我們可以連續發送兩個數字,而不會阻塞,但如果想再發送第三個數字,則會阻塞,直到有goroutine接收。
package main
import "fmt"
func main() {
ch := make(chan int)
// 以下程式碼將導致編譯錯誤
ch <- "Hello" // 嘗試向整數Channel發送String
}
在這個例子中,我們想要在整數型的Channel ch
傳遞一個String,這就會讓編譯器看不懂,進而報錯,因為它們不是相同類型的資料。
2. 不可擴展:一樣在拿紙條來舉例,當你撕下一張紙作為紙條,就不能再決定紙條大小了;Channel也是,建立完後,容量就不能動態擴展。如果需要更多容量,就必須重新建立一個新的Channel。
package main
import (
"fmt"
"time"
)
func main() {
// 建立一個整數型的Channel,容量為2
ch := make(chan int, 2)
// 向Channel發送兩個數字
ch <- 1
ch <- 2
// 嘗試再次向Channel發送,但此時容量已滿,將導致死鎖
// ch <- 3 // 這行會導致程式死鎖
// 讀取Channel中的數字
fmt.Println(<-ch)
fmt.Println(<-ch)
// 關閉Channel
close(ch)
// 再次嘗試讀取Channel中的數字,將得到零值並不會造成死鎖
fmt.Println(<-ch) // 輸出:0
}
Goroutine就像是講電話的我們,Channel就像是話筒,能穩穩地傳達我要的訊息,在goroutines之間安全地傳遞資料和協調操作,也避免了競爭條件和死鎖等問題。