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