聊聊協(xié)程和管道—管道
管道簡(jiǎn)介
【1】管道(channel)特質(zhì)介紹:
(1)管道本質(zhì)就是一個(gè)數(shù)據(jù)結(jié)構(gòu)-隊(duì)列
(2)數(shù)據(jù)是先進(jìn)先出
(3)自身線程安全,多協(xié)程訪問(wèn)時(shí),不需要加鎖,channel本身就是線程安全的
(4)管道有類型的,一個(gè)string的管道只能存放string類型數(shù)據(jù)
管道入門案例
【1】管道的定義:
var 變量名 chan 數(shù)據(jù)類型
PS1:chan管道關(guān)鍵字
PS2:數(shù)據(jù)類型指的是管道的類型,里面放入數(shù)據(jù)的類型,管道是有類型的,int類型的管道只能寫(xiě)入整數(shù)int
PS3:管道是引用類型,必須初始化才能寫(xiě)入數(shù)據(jù),即make后才能使用
【2】案例:
func main() {
//定義管道 、 聲明管道 ---> 定義一個(gè)int類型的管道
var intChan chan int
//通過(guò)make初始化:管道可以存放3個(gè)int類型的數(shù)據(jù)
intChan = make(chan int, 3)
//證明管道是引用類型:
fmt.Printf("intChan的值: %v \n",intChan)
//向管道存放數(shù)據(jù):
intChan <- -10
num := 20
intChan <- num
intChan <- 40
//注意:不能存放大于容量的數(shù)據(jù):
// intChan <- -80
//輸出管道的長(zhǎng)度:
fmt.Printf("管道的實(shí)際長(zhǎng)度:%v,管道的容量是:%v \n",len(intChan),cap(intChan))
//在管道中讀取數(shù)據(jù):
num1 := <-intChan
num2 := <-intChan
num3 := <-intChan
fmt.Println(num1)
fmt.Println(num2)
fmt.Println(num3)
//注意:在沒(méi)有使用協(xié)程的情況下,如果管道的數(shù)據(jù)已經(jīng)全部取出,那么再取就會(huì)報(bào)錯(cuò):
// num4 := <-intChan
// fmt.Println(num4)
fmt.Printf("管道的實(shí)際長(zhǎng)度:%v,管道的容量是:%v \n",len(intChan),cap(intChan))
}
管道的關(guān)閉
【1】管道的關(guān)閉:
使用內(nèi)置函數(shù)close可以關(guān)閉管道,當(dāng)管道關(guān)閉后,就不能再向管道寫(xiě)數(shù)據(jù)了,但是仍然可以從該管道讀取數(shù)據(jù)。
【2】案例:
func main() {
var intChan chan int
intChan = make(chan int, 3)
intChan <- 10
intChan <- 20
//關(guān)閉管道:
close(intChan)
//再次寫(xiě)入數(shù)據(jù):--->報(bào)錯(cuò)
// intChan <- 30
//當(dāng)管道關(guān)閉后,讀取數(shù)據(jù)是可以的:
num := <- intChan
fmt.Println(num)
}
管道的遍歷
【1】管道的遍歷:
管道支持for-range的方式進(jìn)行遍歷,請(qǐng)注意兩個(gè)細(xì)節(jié)
1)在遍歷時(shí),如果管道沒(méi)有關(guān)閉,則會(huì)出現(xiàn)deadlock的錯(cuò)誤
2)在遍歷時(shí),如果管道已經(jīng)關(guān)閉,則會(huì)正常遍歷數(shù)據(jù),遍歷完后,就會(huì)退出遍歷。
【2】案例:
func main() {
var intChan chan int
intChan = make(chan int, 100)
for i := 0; i < 100; i++ {
intChan <- i
}
//在遍歷前,如果沒(méi)有關(guān)閉管道,就會(huì)出現(xiàn)deadlock的錯(cuò)誤
//所以我們?cè)诒闅v前要進(jìn)行管道的關(guān)閉
// for v := range intChan {
// fmt.Println("value = ",v)
// }
close(intChan)
//遍歷:for-range
for v := range intChan {
fmt.Println("value = ",v)
}
}
協(xié)程和管道協(xié)同工作案例
【1】案例需求:
請(qǐng)完成協(xié)程和管道協(xié)同工作的案例,具體要求:
1) 開(kāi)啟一個(gè)writeData協(xié)程,向管道中寫(xiě)入50個(gè)整數(shù).
2) 開(kāi)啟一個(gè)readData協(xié)程,從管道中讀取writeData寫(xiě)入的數(shù)據(jù)。
3) 注意: writeData和readDate操作的是同一個(gè)管道
4) 主線程需要等待writeData和readDate協(xié)程都完成工作才能退出
【2】原理圖:
package main
import (
"fmt"
"time"
"sync"
)
var wg sync.WaitGroup
//寫(xiě):
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i <= 50; i++ {
intChan <- i
fmt.Println("寫(xiě)入的數(shù)據(jù)為:",i)
time.Sleep(time.Second)
}
close(intChan)
}
//讀:
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("讀取的數(shù)據(jù)為:",v)
time.Sleep(time.Second)
}
}
func main() {
//主線程
//寫(xiě)協(xié)程和讀協(xié)程共同操作同一個(gè)管道-》定義管道:
intChan := make(chan int, 50)
wg.Add(2)
//開(kāi)啟讀和寫(xiě)的協(xié)程:
go writeData(intChan)
go readData(intChan)
//主線程一直在阻塞,什么時(shí)候wg減為0了,就停止
wg.Wait()
fmt.Println("讀寫(xiě)數(shù)據(jù)完成...")
}
運(yùn)行結(jié)果:
聲明只讀只寫(xiě)管道
【1】管道可以聲明為只讀或者只寫(xiě)性質(zhì)
【2】代碼:
package main
import (
"fmt"
)
func main() {
//默認(rèn)情況下,管道是雙向的--》可讀可寫(xiě):
//聲明為只寫(xiě):
// 管道具備<- 只寫(xiě)性質(zhì)
var intChan chan<- int
intChan = make(chan int, 3)
intChan <- 10
// 報(bào)錯(cuò)
// num := <- intChan
fmt.Println("intChan:",intChan)
//聲明為只讀:
// 管道具備<- 只讀性質(zhì)
var intChan2 <-chan int
if intChan2 != nil {
num1 := <- intChan2
fmt.Println("num1:",num1)
}
// 報(bào)錯(cuò)
// intChan2 <- 30
}
管道的阻塞
【1】當(dāng)管道只寫(xiě)入數(shù)據(jù),沒(méi)有讀取,就會(huì)出現(xiàn)阻塞:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i < 10; i++ {
intChan <- i
fmt.Println("寫(xiě)入的數(shù)據(jù):",i)
}
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("讀取的數(shù)據(jù)為:",v)
}
}
func main() {
intChan := make(chan int, 10)
wg.Add(2)
go writeData(intChan)
// go readData(intChan)
wg.Wait()
}
運(yùn)行結(jié)果
【2】寫(xiě)的快,讀的慢(管道讀寫(xiě)頻率不一致),不會(huì)出現(xiàn)阻塞問(wèn)題:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func writeData(intChan chan int) {
defer wg.Done()
for i := 1; i < 10; i++ {
intChan <- i
fmt.Println("寫(xiě)入的數(shù)據(jù):",i)
}
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
for v := range intChan {
fmt.Println("讀取的數(shù)據(jù)為:",v)
time.Sleep(time.Second)
}
}
func main() {
intChan := make(chan int, 10)
wg.Add(2)
go writeData(intChan)
go readData(intChan)
wg.Wait()
}
select功能
【1】select功能:解決多個(gè)管道的選擇問(wèn)題,也可以叫做多路復(fù)用,可以從多個(gè)管道中隨機(jī)公平地選擇一個(gè)來(lái)執(zhí)行
PS:case后面必須進(jìn)行的是io操作,不能是等值,隨機(jī)去選擇一個(gè)io操作
PS:default防止select被阻塞住,加入default
【2】代碼:
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int, 1)
go func () {
time.Sleep(time.Second * 15)
intChan <- 15
}()
stringChan := make(chan string, 1)
go func () {
time.Sleep(time.Second * 12)
stringChan <- "hellocyz"
}()
//本身取數(shù)據(jù)就是阻塞的
// fmt.Println(<-intChan)
select {
case v := <-intChan : fmt.Println("intChan:",v)
case v := <-stringChan : fmt.Println("stringChan:",v)
default: fmt.Println("防止select被阻塞")
}
}
defer+recover機(jī)制處理錯(cuò)誤
【1】問(wèn)題原因:多個(gè)協(xié)程工作,其中一個(gè)協(xié)程出現(xiàn)panic,導(dǎo)致程序崩潰
【2】解決辦法:利用defer+recover捕獲panic進(jìn)行處理,即使協(xié)程出現(xiàn)問(wèn)題,主線程仍然不受影響可以繼續(xù)執(zhí)行。
【3】案例:
package main
import (
"fmt"
"time"
)
//輸出數(shù)字:
func printNum() {
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
}
//做除法操作:
func divide() {
defer func () {
err := recover()
if err != nil {
fmt.Println("devide()出現(xiàn)錯(cuò)誤:",err)
}
}()
num1 := 10
num2 := 0
result := num1 / num2
fmt.Println(result)
}
func main() {
//啟動(dòng)兩個(gè)協(xié)程:
go printNum()
go divide()
time.Sleep(time.Second * 5)
}
結(jié)果: