Golang通过通信来实现共享内存,而不是通过共享内存而实现通信,通信实际就是借用channel来实现的
channel底层数据结构
type hchan struct {
qcount uint
dataqsiz uint
buf unsafe.Pointer #是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表
elemsize uint16
closed uint32
elemtype *_type
sendx uint #buf这个循环链表中的发送或接收的index
recvx uint
recvq waitq #接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表
sendq waitq
lock mutex
}
整体结构图:
channel 发送send(ch <- xxx) 和 接收recv(<- ch)的逻辑
如果要让元素先进先出的顺序进入到buf循环链表中,就需要加锁,hchan结构中本身就携带了一个互斥锁mutex。
当使用send (ch <- xx)recv ( <-ch)hchan
具体过程:
加锁->传递数据->解锁
在channel阻塞时,goroutine如何调度
G1 ch := make(chan int,1) ch <-1 #阻塞 ch <-1
sudogsendq
p := <-ch时
G2从缓存队列中取出数据,channel会将等待队列中的G1推出,将G1当时send的数据推到缓存中,然后调用Go的scheduler,唤醒G1,并把G1放到可运行的Goroutine队列中
channel结合select简单使用
所有的case都阻塞时,默认执行default中的值,多个case可以执行时候,随便选择一个case
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x := <-ch:
fmt.Println(x)
case ch <- i:
}
}
输出0,2,4,6,8
某个请求时间过程,通过default释放资源
select {
case <-ch:
// ch1所在的发送端goroutine正在处理请求
case <-time.After(2*time.Second):
// 释放资源,返回请求处理失败的数据,或者先通知用户已处理成功,最终一致性可以保证。
// 最重要的是快速响应,免得用户看着页面没反应,过多的点击按钮发送请求,会过多消耗服务端的系统资源
}
range
package main
import (
"fmt"
)
// 开启5个goroutine对channel中的每个值求立方
func oprate(task chan int, exitch chan bool) {
for t := range task { // 处理任务
fmt.Println("ret:", t*t*t)
}
exitch <- true
}
func main() {
task := make(chan int, 1000) //任务管道
exitch := make(chan bool, 5) //退出管道
go func() {
for i := 0; i < 1000; i++ {
fmt.Println("in:", i)
task <- i
}
close(task)
}()
for i := 0; i < 5; i++ { //启动5个goroutine做任务
go oprate(task, exitch)
}
for i := 0; i < 5; i++ {
<-exitch
}
close(exitch) //关闭退出管道
}
5个goroutine并发对任务管道的1000个值进行处理,in是按顺序的,ret不一定按顺序
只读或者只写channel并不是存在的,但只是作为一种形式存在着,例如可以在函数的参数里定义参数只读或者只写
package main
import (
"fmt"
"time"
)
//只能向chan里写数据
func send(c chan<- int) {
for i := 0; i < 10; i++ {
c <- i
}
}
//只能取channel中的数据
func get(c <-chan int) {
for i := range c {
fmt.Println(i)
}
}
func main() {
c := make(chan int)
go send(c)
go get(c)
time.Sleep(time.Second*1)
}
对channel读写频率的控制,借助time.Tick(time)实现
复制代码
package main
import (
"time"
"fmt"
)
func main(){
requests:= make(chan int ,5)
for i:=1;i<5;i++{
requests<-i
}
close(requests)
limiter := time.Tick(time.Second*1)
for req:=range requests{
<-limiter
fmt.Println("requets",req,time.Now()) //执行到这里,需要隔1秒才继续往下执行,time.Tick(timer)上面已定义
}
}
//结果:
requets 1 2018-07-06 10:17:35.98056403 +0800 CST m=+1.004248763
requets 2 2018-07-06 10:17:36.978123472 +0800 CST m=+2.001798205
requets 3 2018-07-06 10:17:37.980869517 +0800 CST m=+3.004544250
requets 4 2018-07-06 10:17:38.976868836 +0800 CST m=+4.000533569
如何正确关闭channel?
channel 关闭了接着 send 数据会发生panic,关闭一个已经关闭的 channel 会发生panic
The Channel Closing Principle:在使用Go channel的时候,一个适用的原则是不要从接收端关闭channel,也不要关闭有多个并发发送者的channel。换句话说,如果sender(发送者)只是唯一的sender或者是channel最后一个活跃的sender,那么你应该在sender的goroutine关闭channel,从而通知receiver(s)(接收者们)已经没有值可以读了。维持这条原则将保证永远不会发生向一个已经关闭的channel发送值或者关闭一个已经关闭的channel。