教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 golang chan原理

golang chan原理

发布时间:2021-04-23   编辑:jiaochengji.com
教程集为您提供golang chan原理等资源,欢迎您收藏本站,我们将为您提供最新的golang chan原理资源

 

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

 

 
到此这篇关于“golang chan原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Golang channel 使用指南
Golang号称高并发,但高并发时性能不高解决办法
golang chan 关闭时的原则
[golang基础] golang开发思想(future模式)
go语言并发编程
go的goroutine问题
Go语言 | goroutine不只有基础的用法,还有这些你不知道的操作
golang 面试题(十二)chan缓存池
2020-10-19Go语言goroutine和channel
golang channel传递map

[关闭]
~ ~