文章目录
- 1、管道
- 2、管道的定义
- 3、管道的关闭
- 4、管道的遍历
- 5、管道 + 协程
- 6、只读、只写管道
- 7、管道的阻塞
- 8、select
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<- 10num := 20intChan<- numintChan<- 40 //注意:不能存放大于容量的数据://intChan<- 80 //在管道中读取数据:num1 := <-intChannum2 := <-intChannum3 := <-intChanfmt.Println(num1)fmt.Println(num2)fmt.Println(num3)//注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:num4 := <-intChanfmt.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<- 10intChan<- 20//关闭管道:close(intChan)//再次写入数据:--->报错//intChan<- 30//当管道关闭后,读取数据是可以的:num := <- intChanfmt.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-rangefor 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<- ifmt.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 := <-intChan3fmt.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++{ //超过容量管道的容量10intChan<- ifmt.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后就可以读到了