package main
import (
"fmt"
"time"
)
func main() {
//test2()
test4()
time.Sleep(time.Second)
}
func test1() {
//这个地方注意make的时候指定了管道的容量12,假如说只是向管道里面塞数据不取的话
//最多塞12个数据就堵塞住了。如果一边塞数据一边取数据,保证管道中始终有空闲位置的话
//那就可以一直塞数据不会陷入阻塞状态
queue := make(chan int, 12)
//起一个协程,不断循环从管道中取数据,如果管道中没有数据就会陷入阻塞状态
go func() {
for {
data := <-queue
fmt.Println(data, " ")
}
}()
for i := 0; i < 15; i++ {
queue <- i
}
}
func test2() {
/*
管道的写入或者读取可能会阻塞当前协程,因为当前管道是可读还是可写的是不知道的。
有可能当前管道缓冲区已经满了协程还尝试写数据,那就写阻塞;也有可能当前缓冲区为空没有数据,协程还尝试读取数据,那就读阻塞。
这里可以采用golang中的select关键字,同时监听多个管道,非常类似于IO多路复用中的概念,比如epoll。
轮询的方式监听管道,并从管道中读取数据,这样避免阻塞情况的发生。
*/
//搞两个协程,分别往不同的通道写入数据,另有一个主协程轮询从通道中读取数据
c1 := make(chan int, 2)
c2 := make(chan int, 2)
//两个协程异步向两个不同的管道中写入数据
go func() {
for i := 0; i < 1000; i++ {
c1 <- i
//这个地方设置睡眠可能是为了等待主协程执行,轮询取数据,避免一直向管道中塞数据,可能会塞满陷入到写阻塞
//time.Sleep(time.Second)//
}
}()
go func() {
for i := 1000; i < 2000; i++ {
c2 <- i
//time.Sleep(time.Millisecond * 500)
}
}()
//for循环结合select语句监听管道数据,轮询的方式,读协程不会再阻塞了(default)
for {
select {
case data := <-c1:
fmt.Println("data from c1: ", data)
case data := <-c2:
fmt.Println("data from c2: ", data)
default: //当其他分支阻塞的时候,默认执行default
}
}
}
func test3() {
queue := make(chan int, 0) //无缓冲管道(直接读会阻塞)
//设置定时器1秒后触发,触发后相当于解除阻塞,返回可读管道
t := time.After(time.Second)
go func() {
select {
case <-queue:
fmt.Println("从管道取数据正常")
case <-t: //如果select轮询管道中没有数据可取会进入阻塞状态,但是1秒后定时器触发相当于解除阻塞
fmt.Println("timeout") //time.After返回的其实就是管道。1秒后管道t 变为可读
}
}()
time.Sleep(time.Second * 3)
}
func test4() {
/*
管道的声明一般包含传递的数据类型,但是在某些场景下,我们使用管道只想传递一个信号。
比如用程序计数器的话我们并不关心管道t读取的数据,而是关心当前管道是否处于可读或者可写的状态(避免阻塞)
像下面这个程序,主协程要等待子协程运行结束后再退出。这里通过管道实现,管道声明为chan struct{},因为数据不重要了,只关注可读可写状态。
初始的时候主协程读管道数据陷入阻塞,而等到子协程执行完毕后,向管道中写入数据,主协程就会解除阻塞恢复执行。
当然这里管道缓冲区为0,也就是写入一个数据必须马上取走,否则连续写入数据到管道同样阻塞
*/
queue := make(chan struct{}, 0)
go func() {
queue <- struct{}{} //子协程写入数据到管道
//time.Sleep(time.Second)
}()
<-queue //主协程读数据陷入阻塞,等待写协程写入数据到管道后解除阻塞
fmt.Println("time end")
}
管道chan的底层数据结构:
1、存储数据-->循环队列-->数组(unsafe.Pointer)
2、存储阻塞的协程队列-->管道变量-->sudog
3、协程间并发-->锁mutex(互斥锁控制)
// runtime/chan.go
type hchan struct {
//当前管道存储的元素数目
qcount uint // total data in the queue
//管道容量
dataqsiz uint // size of the circular queue
//数组(循环队列做数据缓冲区)
buf unsafe.Pointer // points to an array of dataqsiz elements
//标识管道是否被close
closed uint32
//管道存储的元素类型 & 元素大小
elemtype *_type // element type
elemsize uint16
//读/写索引,循环队列
sendx uint // send index
recvx uint // receive index
//读阻塞协程队列,写协程堵塞队列
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
//锁 互斥锁
lock mutex
}