1、管道
- channel
- 本质是一个队列,先进先出
- 自身线程安全,多协程访问时,不用加锁,channel本身就是线程安全的
- 一个string的管道只能存放string类型数据
2、管道的定义
var 变量名 chan 数据类型
- chan是管道的关键字
- 数据类型,比如int类型的管道只能写入整数int
- 管道是引用类型,必须初始化才能写入数据,即make后才能使用
- 管道中不能存放大于容量的数据
在没有使用协程的情况下
,如果管道的数据已经全部取出,那么再取就会报错
示例:
package main
import(
"fmt"
)
func main(){
//定义管道 、 声明管道 ---> 定义一个int类型的管道
var intChan chan int
//通过make初始化:管道可以存放3个int类型的数据
intChan = make(chan int,3)
//证明管道是引用类型:
fmt.Printf("intChan的值:%v",intChan) // 0xc000112080
//向管道存放数据:
intChan<- 10
num := 20
intChan<- num
intChan<- 40
//注意:不能存放大于容量的数据:
//intChan<- 80
//在管道中读取数据:
num1 := <-intChan
num2 := <-intChan
num3 := <-intChan
fmt.Println(num1)
fmt.Println(num2)
fmt.Println(num3)
//注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:
num4 := <-intChan
fmt.Println(num4)
//输出管道的长度:
fmt.Printf("管道的实际长度:%v,管道的容量是:%v",len(intChan),cap(intChan))
}
3、管道的关闭
使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据
package main
import(
"fmt"
)
func main(){
//定义管道 、 声明管道
var intChan chan int
//通过make初始化:管道可以存放3个int类型的数据
intChan = make(chan int,3)
//在管道中存放数据:
intChan<- 10
intChan<- 20
//关闭管道:
close(intChan)
//再次写入数据:--->报错
//intChan<- 30
//当管道关闭后,读取数据是可以的:
num := <- intChan
fmt.Println(num)
}
4、管道的遍历
- for
- for-range
在遍历时,如果管道没有关闭,则报错:
package main
import(
"fmt"
)
func main(){
//定义管道 、 声明管道
var intChan chan int
//通过make初始化:管道可以存放100个int类型的数据
intChan = make(chan int,100)
for i := 0;i < 100;i++ {
intChan<- i
}
//在遍历前,如果没有关闭管道,就会出现deadlock的错误
//所以我们在遍历前要进行管道的关闭
close(intChan)
//遍历:for-range
for v := range intChan {
fmt.Println("value = ",v)
}
}
5、管道 + 协程
- 开启一个writeData协程,向管道中写入50个整数.
- 开启一个readData协程,从管道中读取writeData写入的数据
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()
}
运行:
在没有使用协程的情况下
,如果管道的数据已经全部取出,那么再取就会报错。所以上面即使写入后sleep两秒,读取也不会报错。
6、只读、只写管道
package main
import(
"fmt"
)
func main(){
//默认情况下,管道是双向的--》可读可写:
//var intChan1 chan int
//声明为只写:
var intChan2 chan<- int // 管道具备<- 只写性质
intChan2 = make(chan int,3)
intChan2<- 20
//num := <-intChan2 报错
fmt.Println("intChan2:",intChan2)
//声明为只读:
var intChan3 <-chan int// 管道具备<- 只读性质
if intChan3 != nil { //非空
num1 := <-intChan3
fmt.Println("num1:",num1)
}
//intChan3<- 30 报错
}
7、管道的阻塞
管道只写入数据,不读取,超过容量:
package main
import(
"fmt"
_"time"
"sync"
)
var wg sync.WaitGroup //只定义无需赋值
//写:
func writeData(intChan chan int){
defer wg.Done()
for i := 1;i <= 20;i++{ //超过容量管道的容量10
intChan<- i
fmt.Println("写入的数据为:",i)
//time.Sleep(time.Second)
}
//管道关闭:
close(intChan)
}
func main(){//主线程
//定义管道:
intChan := make(chan int,10)
wg.Add(1)
//开启写的协程:
go writeData(intChan)
wg.Wait()
}
改一下,写的快,读的慢:没报错,但很明显,写被影响了,到最后被动等读协程,读走一个,写协程才能继续写。
8、select
- 从多个管道中进行非阻塞的选择
- select同时监听多个管道的数据流通,并在其中任意一个管道有数据可读或者可写的时候做相应的处理
select {
case <- channel1:
// 处理channel1的数据
case data := <- channel2:
// 处理channel2的数据
case channel3 <- value:
// 向channel3发送数据
default:
// 当没有任何通道准备就绪时执行default块
}
按顺序检查有没那个case后面的管道是可读或者可写的,有则执行该case语句。如果多个case同时满足,Go会随机选择一个执行。
如果没有任何case语句满足条件,且存在default语句,则执行default块中的代码。如果没有default语句,则select语句会被阻塞,直到至少有一个case语句满足条件。
package main
import(
"fmt"
"time"
)
func main(){
//定义一个int管道:
intChan := make(chan int,1)
go func(){
time.Sleep(time.Second * 5)
intChan<- 10
}()
//定义一个string管道:
stringChan := make(chan string,1)
go func(){
time.Sleep(time.Second * 2)
stringChan<- "msbgolang"
}()
//fmt.Println(<-intChan)//本身取数据就是阻塞的
select{
case v := <-intChan :
fmt.Println("intChan:",v)
case v := <-stringChan :
fmt.Println("stringChan:",v)
default:
fmt.Println("防止select被阻塞")
}
}
输出:
防止select被阻塞
上面两个case的协程都不能立即可读或可写,走了default。没有default,则输出:
msbgolang
因为其对应的管道阻塞时间短,2s后就可以读到了