在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
channel是Golang在语言层面提供的goroutine间的通信方式,channel主要用于进程内各goroutine间通信,了解channel结构,与goroutine访问机制,程序就能很灵活的实现并发编程。 channel的数据结构如下: type hchan struct { qcount uint // 当前队列中剩余元素个数 dataqsiz uint // 环形队列长度,即可以存放的元素个数 buf unsafe.Pointer // 环形队列指针 elemsize uint16 // 每个元素的大小 closed uint32 // 标识关闭状态 elemtype *_type // 元素类型 sendx uint // 队列下标,指示元素写入时存放到队列中的位置 recvx uint // 队列下标,指示元素从队列的该位置读出 recvq waitq // 等待读消息的goroutine队列 sendq waitq // 等待写消息的goroutine队列 lock mutex // 互斥锁,chan不允许并发读写 } 从channel读数据,如果channel缓冲区为空或者没有缓冲区,当前goroutine会被阻塞。 向channel写数据,如果channel缓冲区已满或者没有缓冲区,当前goroutine会被阻塞。 被阻塞的goroutine将会挂在channel的等待队列中: 因读阻塞的goroutine会被向channel写入数据的goroutine唤醒; 因写阻塞的goroutine会被从channel读数据的goroutine唤醒; 下图展示了一个没有缓冲区的channel,有几个goroutine阻塞等待读数据:
一般情况下,recvq和sendq至少有一个为空,上图recvq中取数据的在火急火燎排队等,buf缓存区一有数据就会被拿走,怎么会有sendq排队不去buf缓存区存放数据了,实际上这种情况数据根本不会要存到buf缓存区的,直接从你sendq队列goroutine的数据区就取走了。相似的如果recvq队列为空,sendq队列排队的情况也可同样的机制。 从上面了解goroutine要从channel访问数据,如果遇到为空或满了的情况,该channel就要排队,但也有例外,这情况下,它只是来看一下是否能访问(队列是否为空),如果不能,它下次再来。这个就可以给我们站台排队买票来理解: package main import ( "fmt" "time" ) func releaseTickets(ticketNumchan int) { for { time.Sleep(1 * time.Second)//假设隔一秒钟放一张票 ticketNum <-1 } } func ticketTout(ticketNumchan int) { for { time.Sleep(100 * time.Millisecond)//黄牛党,0.1秒轮询 fmt.Println("黄牛:我排队咯") tickets := <- ticketNum//因为放票时距是1秒,部分黄牛也会阻塞 fmt.Printf("黄牛:又买到%d张票啦\n",tickets) } } func main() { var ticketBuf = make(chan int,10) go releaseTickets(ticketBuf) go ticketTout(ticketBuf) ticket :=0 for { if ticket ==1 { break //普通购票者买到票了,回家 } time.Sleep(300 * time.Millisecond)//普通购票者,0.3秒轮询 select { case ticket = <- ticketBuf ://队伍太长了,不想排队,等下再来看看 fmt.Printf("普通购票者:也买到%d张票啦\n",ticket)//基本不可能 default: fmt.Println("普通购票者:唉,不排队就买不到票") } } } 这种情况下,普通购票者不进队列排队,而黄牛不停的进入队列排队,所以买不着票。 case ticket = <- ticketBuf : 这个case语句执行<- ticketBuf (抢票)是不进入队列,如果不行就返回了。 select的case语句读channel不会阻塞,尽管channel中没有数据。这是由于case语句编译后调用读channel时会明确传入不阻塞的参数,此时读不到数据时不会将当前goroutine加入到等待队列,而是直接返回。 那么如何才能买到票了,又不想把身份证交给黄牛,同时黄牛加价也吓人。有一种办法是叫自己的有时间的朋友(代理人)排队代买啦,然后你就不停打电话问朋友是否买到票,这样就能实现不用在现场排队,又能买到票,落下心来,好安排计划。 package main import ( "fmt" "time" ) func releaseTickets2(ticketNumchan int) { for { time.Sleep(1 * time.Second)//假设隔一秒钟放一张票 ticketNum <-1 } } func ticketTout2(ticketNumchan int) { for { time.Sleep(100 * time.Millisecond)//黄牛党,0.1秒轮询 fmt.Println("黄牛:我排队咯") tickets := <- ticketNum//因为放票时距是1秒,部分黄牛也会阻塞 fmt.Printf("黄牛:又买到%d张票啦\n",tickets) } } func proxyMan(ticketNumchan int,ticketchan int) { //for { time.Sleep(2 * time.Second)//佛系代理人,2秒轮询, //但只要排队还是能抢到票的 ticket <- <-ticketNum//代理人阻塞排队 //} } func main() { var ticketBuf = make(chan int,10) var mobAboutTicket = make(chan int,2) go releaseTickets2(ticketBuf) go ticketTout2(ticketBuf) go proxyMan(ticketBuf,mobAboutTicket)//代理人进程 ticket :=0 for { if ticket ==1 {//电话通道里告诉已经买到票了 fmt.Println("普通购票者:朋友帮我买到票了") break //普通购票者买到票了,回家 } time.Sleep(300 * time.Millisecond) select { //case <- ticketBuf : //委托给代理人了,自己就不在现场争通道了 //代理人在代理人的进程中随队争,ticketBuf通过参数传给ticketNum case ticket = <- mobAboutTicket ://与代理的专用通道,这个不会有其它人来抢了, //非阻塞模式访问都可取到,相当于不停打电话问 //因为这个电话两人专用电话,不存在打不进 fmt.Printf("代理购票者:也买到%d张票啦\n",ticket) default: fmt.Println("普通购票者:唉,不排队就买不到票") } } } 以上部分内容来自Go语言中文网,里面一些概念讲得比较清楚,可可去看看。 |
请发表评论