写点什么

写了一年 golang,来聊聊进程、线程与协程,javamap 底层原理

用户头像
极客good
关注
发布于: 刚刚

在进程切换时需要转换内存地址空间,而线程切换没有这个动作,所以线程切换比进程切换代价更小。


为什么内存地址空间转换这么慢?Linux 实现中,每个进程的地址空间都是虚拟的,虚拟地址空间转换到物理地址空间需要查页表,这个查询是很慢的过程,因此会用一种叫做 TLB 的 cache 来加速,当进程切换后,TLB 也随之失效了,所以会变慢。


综上,线程是为了降低进程切换过程中的开销。

协程

当我们的程序是 IO 密集型时(如 web 服务器、网关等),为了追求高吞吐,有两种思路:


  1. 为每个请求开一个线程处理,为了降低线程的创建开销,可以使用线程池技术,理论上线程池越大,则吞吐越高,但线程池越大,CPU 花在切换上的开销也越大


线程的创建、销毁都需要调用系统调用,每次请求都创建,高并发下开销就显得很大,而且线程占用内存是 MB 级别,数量不能太多


为什么线程越多 cpu 切换越多?准确来说是可执行的线程越多,cpu 切换越多,因为操作系统的调度要保证绝对公平,有可执行线程时,一定是要雨露均沾,所以切换次数变多


  1. 使用异步非阻塞的开发模型,用一个进程或线程接收请求,然后通过 IO 多路复用让进程或线程不阻塞,省去上下文切换的开销


这两个方案,优缺点都很明显,方案 1 实现简单,但性能不高;方案 2 性能非常好,但实现起来复杂。有没有介于这两者之间的方案?既要简单,又要性能高,协程就解决了这个


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


问题。


协程是用户视角的一种抽象,操作系统并没有这个概念,其主要思想是在用户态实现调度算法,用少量线程完成大量任务的调度。


协程需要解决线程遇到的几个问题:


  • 内存占用要小,且创建开销要小

  • 减少上下文切换的开销


第一点好实现,用户态的协程,只是一个数据结构,无需系统调用,而且可以设计的很小,达到 KB 级别。


第二点只能减少上下文切换次数来解决,因为协程的本质还是线程,其切换开销在用户态是无法降低的,只能通过降低切换次数来达到总体上开销的减少,可以有如下手段:


  1. 让可执行的线程尽量少,这样切换次数必然会少

  2. 让线程尽可能的处于运行状态,而不是阻塞让出时间片

Goroutine

goroutine 是 golang 实现的协程,其特点是在语言层面就支持,使用起来非常方便,它的核心是 MPG 调度模型:


  • M:内核线程

  • P:处理器,用来执行 goroutine,它维护了本地可运行队列

  • G:goroutine,代码和数据结构

  • S:调度器,维护 M 和 P 的信息


除此之外还有一个全局可运行队列。



  1. 在 golang 中使用 go 关键字启动一个 goroutine,它将会被挂到 P 的 runqueue 中,等待被调度



  1. 当 M0 中正在运行的 G0 阻塞时(如执行了一个系统调用),此时 M0 会休眠,它将放弃挂载的 P0,以便被其他 M 调度到

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
写了一年golang,来聊聊进程、线程与协程,javamap底层原理