golang chan原理
channel 是实现go语言所谓CSP理念的重点。在进程中通信有多重方式。共享内存,消息队列,socket等方式。而channel是在同一个进程内不同协程之间的通信方式。CSP:CSP模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。
channel结构体
Channel 实际上是个环形队列。实际的队列空间就在这个channel结构体之后申请的空间。dataqsiz -> data queue size 队列大小。elemsize 元素的大小。Lock用来保证线程(协程)安全。recvq和sendq分别用来保存对应的阻塞队列。
struct Hchan
{
uintgo qcount; // 队列q中的总数据数量
uintgo dataqsiz; // 环形队列q的数据大小
uint16 elemsize;
bool closed;
uint8 elemalign;
Alg* elemalg; // interface for element type
uintgo sendx; // 发送index
uintgo recvx; // 接收index
WaitQ recvq; // 因recv而阻塞的等待队列
WaitQ sendq; // 因send而阻塞的等待队列
Lock;
};
例子
func main(){
//带缓冲的channel
ch := make(chan Task, 3)
numWorkers := 10
//启动固定数量的worker
for i := 0; i< numWorkers; i++ {
go worker(ch)
}
//发送任务给worker
hellaTasks := getTaks()
for _, task := range hellaTasks {
ch <- task
}
...
}
func worker(ch chan Task){
for {
//接受任务
task := <- ch
process(task)
}
}
以上是最长用的channel的例子,参考kavya的解析。
goroutine和channel之间的数据的通过copy完成的(有特例)。发送的时候从G1 copy到channel中。接收的时候从channel中copy到G2内。
send
发送到channel上面去,还有buf则放入。没有则放入到对应的sendq等待队列中。
recv
从buf中取数据。没有则放入到对应的recvq等待队列中。
关于阻塞队列
关于放入阻塞队列到底是如何实现的。需要了解到GO的调度。Go语言调度有3个比较重要的概念。对于OS来说,一般是进程调度,线程调度。里面有调度器,调度算法等来实现,现在比较经典的是公平调度算法,详细见 https://blog.csdn.net/u012279631/article/details/77677266。go语言相当于把这个权利自己拿过来了。在用户态自己调用。在线程之下挂载了协程 称之为G(goroutine),而线程称之为M(machine 即底层),具体调度者称之为P(context)。这是因为进程切换上下文耗费的系统资源大,创建线程的时候耗费8K内存。当在高并发的情况下去处理对应的事务,如果用线程去处理仍然对资源非常浪费。从这个角度来讲,golang就是为了处理高并发而特定的一种语言。
线程和协程映射关系
P从runable队列中取到相应的G,放到M中取执行。
阻塞情形
channel队列满 无法放入阻塞
当channel已经满了,仍然有数据发送到channel中时。
原本状态,G1在running中。
发现无法放入channel中,导致阻塞。
这时候会从runable队列中取下一个goroutine,放到M中去执行。
再回头看channel相应的改变。因为buffer已经满了。所以会把它放在sendq中。
结构如下:
当有goroutine去取对应buffer时,清空一个buffer。就会把sendx里面的elem内容copy到对应的channel buffer中。然后把goroutine状态设置为run,并且放回到runable队列中。
channel队列空 无法读取阻塞
这里用了非常聪明的方式减少了一次内存的拷贝。
当G1发送内容到channel的时候,首先查看recvq队列是否有阻塞的goroutine。如果有则直接从G1copy到G2。优化了从G1 -> channel ->G2这个步骤。
参考链接:
https://speakerdeck.com/kavya719/understanding-channels
https://tiancaiamao.gitbooks.io/go-internals/content/zh/07.1.html