Go關鍵字--chan
chan
chan又稱之為通道,形式類似於管道,內容從一頭被送進去,從另一頭被讀取出來。下邊來介紹定義通道的方法:
var 變數名 chan dataType
定義通道時,需要指定資料型別,就是隻允許這個指定資料型別的變數通過這個通道。
初始化通道
golang中在初始化通道型別變數時,可以將通道分為兩種情況,一種是帶緩衝的通道,另一種是不帶緩衝的通道。 下邊來介紹下兩種情況的初始化方法:
// 初始化不帶緩衝的通道,通道中資料型別是int
var ch1 = make(chan int)
// 初始化帶10個緩衝的通道,通道中資料型別是string
var ch2 = make (chan string,10)
還有一種寫法是,定義並初始化通道,
// 定義通道,並給通道初始化8個緩衝
ch3 := make(chan int ,8)
// 定義通道,並初始化為不帶緩衝通道
ch4 := make(chan string)
通道賦值
對通道的讀取和寫入都可能進入阻塞狀態。
- 不帶緩衝的通道,在寫入時,就會發生阻塞,直到通道中資訊被讀取後,才會結束阻塞。
- 帶緩衝的通道,每次向通道中寫入一次資訊,通道長度就會加1,每成功從通道讀取一次資訊,通道長度減1。如果通道長度等於通道緩衝長度時,向通道繼續寫入資訊會使程式阻塞;如果通道長度小於通道緩衝長度,則向通道中寫入資訊不會造成阻塞。假如通道長度是5,那麼在通道沒有被讀取的情況下,向通道中第6次寫入資訊時才會導致程式阻塞。
通道寫入的語法格式是:
var ch = make(chan string,10)
// 將字串”hello"寫入到通道中,通道長度加1
ch <- "hello"
讀取通道
通道為空 1. 通道沒有關閉,程式會進入阻塞狀態,等到通道有資訊寫入 2. 通道已經關閉,不會阻塞,返回通道中資料型別初始值(髒資料),如通道是chan int時,返回值是0,通道是chan string時,返回值是空。 通道不為空 1. 通道沒有關閉,從通道中讀取一次資訊,讀取完成後,往下執行 2. 通道已被關閉,從通道中讀取一次信信,讀取完成後,往下執行
讀取通道操作:
val,ok := <-ch
使用斷言讀取通道中的值,檢查通道是否還有內容,以及判斷通道是否已經關閉,當通道中沒有資訊,且通道已經關閉時,ok值為false,當通道沒有關閉,但是通道中沒有資訊,程式將會阻塞,如果通道中有內容,則ok值是true。
另一種不使用斷言的方式讀取通道
val := <-ch
寫入與讀取通道
讀取不帶緩衝的通道示例方法:
package main
import (
"fmt"
)
func main() {
// 定義一個不帶緩衝的通道,通道中資料型別是int
var c = make(chan int)
// 開啟一個攜程,讀取通道中的內容
go func() {
fmt.Println("寫入資訊是:", <-c)
}()
// 向通道中寫入資料
c <- 1
}
輸出結果:
寫入資訊是: 1
當對帶緩衝的通道進行讀寫時,只要通道中資料長度不大於緩衝長度,就不會出現阻塞,但是讀取帶緩衝的通道,通道中沒有內容時,程式依然會進入阻塞狀態。所以,帶緩衝的通道,只對寫入產生影響。下邊來一個示例:
package main
import (
"fmt"
)
func main() {
var c = make(chan int, 3)
c <- 1
c <- 2
c <- 3
//c <- 4
fmt.Println("end")
}
輸出資訊是:
end
當向帶3個緩衝的通道中寫入內容時,由於只寫入了3次,通道的長度剛好等於緩衝的長度,程式沒有阻塞,當將 c <- 4 前邊的註釋去掉後,由於沒有程式去讀取這個通道,主程式進入死鎖狀態而導致異常。
協程通訊
通道型別變數的實質上是一個地址,如下邊示例程式碼:
package main
import (
"fmt"
)
func main() {
var c = make(chan int, 3)
fmt.Println(c)
}
輸出結果:
0xc042072080
所以,當通道型別變數當做引數傳入函式後,在函式中可以直接對通道中的值進行修改。雖然chan型別變數是一個地址,但是golang不允許使用取值操作符( * )來操作chan型別變數。但是如果你先對chan型別變數使用取地址操作符(&),然後再使用取值操作符(*),這種操作方法還是可以正常執行的,但是這意義不大,除非你的目的是在函式呼叫中,重新定義一個chan型別變數替換原來的變數。
chan的這些特性,可以很好的實現協程之間的同步功能。不帶緩衝的通道,是一種零容忍的等待,可以實現強制同步;帶緩衝的通道,是有一定量容忍度的等待,可以實現允許有一定時間差的同步。
簡單的協程間通訊例子:
package main
import (
"fmt"
"time"
)
func main() {
var c = make(chan int)
go func() {
fmt.Println("待命模式:")
// 讀取通道時產生阻塞,等待其他協程向通道寫入資訊
fmt.Println("命令程式碼是:", <-c)
}()
go func() {
// 延時3秒,向通道中寫入資訊
time.Sleep(time.Second * 3)
fmt.Println("傳送命令:")
c <- 8
close(c)
}()
time.Sleep(time.Second * 5)
fmt.Println("執行完成")
}
輸出資訊是:
待命模式:
傳送命令:
命令程式碼是: 8
執行完成