CSP并发模型
CSP并发模型它不关注发送消息的实体,而关注与发送消息时使用的channel。go语言借用了 process和channel这两个概念。process是在go语言上的表现就是 goroutine,是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。
Golang实现了 CSP 并发模型做为并发基础,底层使用goroutine做为并发实体,goroutine非常轻量级可以创建几十万个实体。实体间通过 channel 继续匿名消息传递使之解耦,在语言层面实现了自动调度,这样屏蔽了很多内部细节,对外提供简单的语法关键字,大大简化了并发编程的思维转换和管理线程的复杂性。
Go调度器
首先GPM是golang runtime里面的东西,是语言层面的实现。也就是说go实现了自己的调度系统。 理解了这一点 再往下看
调度器的组成 => G P M
M(machine)是runtime对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;
P管理着一组Goroutine队列,P里面一般会存当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停 运行后续的goroutine等等。。)当自己的队列消耗完了 会去全局队列里取, 如果全局队列里也消费完了 会去其他P对立里取。
G 很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
GPM协同工作 组成了runtime的调度器。
GPM数量关系
P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。
P的个数是通过runtime.GOMAXPROCS设定的,现在一般不用自己手动设,默认物理线程数(比如我的6核12线程, 值会是12)。 在并发量大的时候会增加一些P和M,但不会太多,切换太频繁的话得不偿失。内核线程的数量一般大于12这个值, 不要错误的认为M与物理线程对应,M是与内核线程对应的。 如果服务器没有其他服务的话,M才近似的与物理线程一一对应。
GO调度优势总结
说了这么多。初步了解了go的调度,我想大致也明白了, 单从线程调度讲,go比起其他语言的优势在哪里了? go的线程模型是M:N的。 其一大特点是goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变)。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。