眾所周知,Go lang的作用域相對(duì)嚴(yán)格,數(shù)據(jù)之間的通信往往要依靠參數(shù)的傳遞,但如果想在多個(gè)協(xié)程任務(wù)中間做數(shù)據(jù)通信,就需要通道(channel)的參與,我們可以把數(shù)據(jù)封裝成一個(gè)對(duì)象,然后把這個(gè)對(duì)象的指針傳入某個(gè)通道變量中,另外一個(gè)協(xié)程從這個(gè)通道中讀出變量的指針,并處理其指向的內(nèi)存對(duì)象。
成都創(chuàng)新互聯(lián)專注于永定網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供永定營(yíng)銷型網(wǎng)站建設(shè),永定網(wǎng)站制作、永定網(wǎng)頁(yè)設(shè)計(jì)、永定網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造永定網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供永定網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
package main
import "fmt"
func main() {
var a chan int
if a == nil {
fmt.Println("通道是空的, 不能使用,需要先創(chuàng)建通道")
a = make(chan int)
fmt.Printf("數(shù)據(jù)類型是: %T", a)
}
}
這里注意,通道聲明之后還需要進(jìn)行創(chuàng)建。
也可以通過海象操作符聲明并創(chuàng)建:
package main
import "fmt"
func main() {
a := make(chan int)
fmt.Printf("數(shù)據(jù)類型是: %T", a)
}
程序返回:
數(shù)據(jù)類型是: chan int%
如此,一個(gè)類型為整形的通道就創(chuàng)建好了。
此外,通道是引用數(shù)據(jù)類型:
package main
import (
"fmt"
)
func main() {
ch1 := make(chan int)
fmt.Printf("%T,%p\n", ch1, ch1)
test1(ch1)
}
func test1(ch chan int) {
fmt.Printf("%T,%p\n", ch, ch)
}
程序返回:
chan int,0xe060
chan int,0xe060
可以看到,在test1函數(shù)內(nèi)和main函數(shù)內(nèi)通道的地址是一樣的,所以他們指向的都是同一個(gè)通道。
通道創(chuàng)建之后,即可以在協(xié)程之間充當(dāng)橋梁:
package main
import "fmt"
func job(ch1 chan int) {
ch1 <- 1
}
func main() {
ch1 := make(chan int)
fmt.Println(ch1)
go job(ch1)
data := <-ch1 // 從ch1通道中讀取數(shù)據(jù)
fmt.Println("data-->", data)
fmt.Println("main。。over。。。。")
}
這里我們聲明一個(gè)函數(shù)job,把通道作為參數(shù)傳遞進(jìn)去,注意這里參數(shù)類型除了聲明通道本身以外,還得聲明通道具體的數(shù)據(jù)類型。
隨后在main函數(shù)中,可以理解為主協(xié)程,創(chuàng)建通道ch1,執(zhí)行開啟協(xié)程任務(wù)job,在job函數(shù)內(nèi),往通道內(nèi)傳遞數(shù)字1
接著,主協(xié)程獲取通道內(nèi)由job協(xié)程傳遞的數(shù)據(jù):
0xa060
data--> 1
main。。over。。。。
藉此,就完成了數(shù)據(jù)的傳遞。
這里需要注意通道的調(diào)用語法:
data := <- a // 讀取通道
a <- data // 寫入通道
這里需要注意的是,通道無論是寫入還是讀取,都是同步阻塞機(jī)制。即當(dāng)有協(xié)程對(duì)通道進(jìn)行操作的時(shí)候,其他協(xié)程都處于“等待”狀態(tài),說白了,就是在“排隊(duì)”,在之前的一篇:并發(fā)與并行,同步和異步,Go lang1.18入門精煉教程,由白丁入鴻儒,Go lang并發(fā)編程之GoroutineEP13,我們要么通過sync.WaitGroup來阻塞主協(xié)程,或者通過time.Sleep(time.Second)方法來阻塞,就是怕主協(xié)程提前執(zhí)行完,早成子協(xié)程來不及執(zhí)行。
而通道的出現(xiàn),就間接幫我們實(shí)現(xiàn)了“阻塞”主協(xié)程的目的。
比如,多個(gè)協(xié)程任務(wù)操作一個(gè)變量:
package main
import (
"fmt"
)
func job1(number int, squareop chan int) {
sum := 20
sum += number
squareop <- sum
}
func job2(number int, cubeop chan int) {
sum := 10
sum += number
cubeop <- sum
}
func main() {
number := 0
ch1 := make(chan int)
ch2 := make(chan int)
go job1(number, ch1)
go job2(number, ch2)
num1, num2 := <-ch1, <-ch2
fmt.Println("Final output", num1+num2)
}
這里job1和job2兩個(gè)協(xié)程任務(wù)同時(shí)異步執(zhí)行,操作number變量,累加后往通道中寫入,程序返回:
Final output 30
理論上,如果是并發(fā)執(zhí)行,返回值應(yīng)該是20或者10,但由于通道的存在,造成協(xié)程任務(wù)阻塞,變回了同步執(zhí)行,所以返回了30。
同時(shí),我們需要注意死鎖問題,如果一個(gè)協(xié)程任務(wù)在一個(gè)通道上發(fā)送數(shù)據(jù),那么其他的協(xié)程任務(wù)應(yīng)該接收數(shù)據(jù),如果這種情況不發(fā)生,那么程序?qū)⒃谶\(yùn)行時(shí)出現(xiàn)死鎖。
換句話說,你發(fā)送了,就得有人接收,只發(fā)不接,或者只收不發(fā),都會(huì)變成死鎖。
此外,協(xié)程任務(wù)可以通過close(ch)方法來關(guān)閉通道:
package main
import (
"fmt"
)
func job(ch1 chan int) {
// 發(fā)送方:3條數(shù)據(jù)
for i := 0; i < 3; i++ {
ch1 <- i //將i寫入通道中
}
close(ch1) //將ch1通道關(guān)閉了。
}
func main() {
ch1 := make(chan int)
go job(ch1)
/*
子goroutine,寫出數(shù)據(jù)3個(gè)
每寫一個(gè),阻塞一次,主程序讀取一次,解除阻塞
主goroutine:循環(huán)讀
每次讀取一個(gè),堵塞一次,子程序,寫出一個(gè),解除阻塞
發(fā)送發(fā),關(guān)閉通道的--->接收方,接收到的數(shù)據(jù)是該類型的零值,以及false
*/
//主程序中獲取通道的數(shù)據(jù)
for {
v, ok := <-ch1 //其他goroutine,顯示的調(diào)用close方法關(guān)閉通道。
if !ok {
fmt.Println("已經(jīng)讀取了所有的數(shù)據(jù),", ok)
break
}
fmt.Println("取出數(shù)據(jù):", v, ok)
}
fmt.Println("main...over....")
}
這里將0到2寫入chl通道,然后關(guān)閉通道。主函數(shù)里有一個(gè)死循環(huán)。類似while,它輪詢通道是否在發(fā)送數(shù)據(jù)后,使用變量ok進(jìn)行判斷。如果ok是假的,則意味著通道關(guān)閉,因此循環(huán)結(jié)束,否則將會(huì)繼續(xù)進(jìn)行無限輪詢。
select 是 Go lang里面的一個(gè)流程控制結(jié)構(gòu),和switch關(guān)鍵字差不多,但是select會(huì)隨機(jī)執(zhí)行一個(gè)可運(yùn)行的通道通信,如果沒有通道通信可運(yùn)行,它將阻塞,直到有通道通信可運(yùn)行:
package main
import (
"fmt"
"time"
)
func job(ch1 chan int) {
time.Sleep(2 * time.Second)
ch1 <- 200
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go job(ch1)
go job(ch2)
select {
case num1 := <-ch1:
fmt.Println("ch1中取數(shù)據(jù)。。", num1)
case num2, ok := <-ch2:
if ok {
fmt.Println("ch2中取數(shù)據(jù)。。", num2)
} else {
fmt.Println("ch2通道已經(jīng)關(guān)閉。。")
}
}
}
這里select會(huì)隨機(jī)選擇一個(gè)可運(yùn)行的通道通信邏輯,可能是ch1通道,也有可能是ch2通道:
? mydemo git:(master) ? go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch1中取數(shù)據(jù)。。 200
? mydemo git:(master) ? go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch1中取數(shù)據(jù)。。 200
? mydemo git:(master) ? go run "/Users/liuyue/wodfan/work/mydemo/hello.go"
ch2中取數(shù)據(jù)。。 200
? mydemo git:(master) ?
綜上,Golang的通道其實(shí)就是將協(xié)程任務(wù)進(jìn)行隔離,編寫并發(fā)邏輯時(shí),關(guān)注通道即可,說白了,Golang的通道就是Python多進(jìn)程通信中的管道,Golang雖然沒有顯性的多進(jìn)程調(diào)用,但其協(xié)程調(diào)度底層就是多進(jìn)程之間的通信,因?yàn)橹挥卸噙M(jìn)程才可能利用CPU的多核資源。