写了一年golang,来聊聊进程、线程与协程
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。
进程
在早期的单任务计算机中,用户一次只能提交一个作业,独享系统的全部资源,同时也只能干一件事情。进行计算时不能进行 IO 读写,但 CPU 与 IO 的速度存在巨大差异,一个作业在 CPU 上所花费的时间非常少,大部分时间在等待 IO。
为了更合理的利用 CPU 资源,把内存划分为多块,不同程序使用各自的内存空间互不干扰,这里单独的程序就是一个进程,CPU 可以在多个进程之间切换执行,让 CPU 的利用率变高。
为了实现 CPU 在多个进程之间切换,需要保存进程的上下文(如程序计数器、栈、内核数据结构等等),以便下次切换回来可以恢复执行。还需要一种调度算法,Linux 中采用了基于时间片和优先级的完全公平调度算法。
线程
多进程的出现是为了解决 CPU 利用率的问题,那为什么还需要线程?答案是为了减少上下文切换时的开销。
进程在如下两个时间点可能会让出 CPU,进行 CPU 切换:
- 进程阻塞,如网络阻塞、代码层面的阻塞(锁、sleep等)、系统调用等
- 进程时间片用完,让出 CPU
而进程切换 CPU 时需要进行这两步:
- 切换页目录以使用新的地址空间
- 切换内核栈和硬件上下文
进程和线程在 Linux 中没有本质区别,他们最大的不同就是进程有自己独立的内存空间,而线程(同进程中)是共享内存空间。
在进程切换时需要转换内存地址空间,而线程切换没有这个动作,所以线程切换比进程切换代价更小。
为什么内存地址空间转换这么慢?Linux 实现中,每个进程的地址空间都是虚拟的,虚拟地址空间转换到物理地址空间需要查页表,这个查询是很慢的过程,因此会用一种叫做 TLB 的 cache 来加速,当进程切换后,TLB 也随之失效了,所以会变慢。
综上,线程是为了降低进程切换过程中的开销。
协程
当我们的程序是 IO 密集型时(如 web 服务器、网关等),为了追求高吞吐,有两种思路:
- 为每个请求开一个线程处理,为了降低线程的创建开销,可以使用线程池技术,理论上线程池越大,则吞吐越高,但线程池越大,CPU 花在切换上的开销也越大
线程的创建、销毁都需要调用系统调用,每次请求都创建,高并发下开销就显得很大,而且线程占用内存是 MB 级别,数量不能太多
为什么线程越多 cpu 切换越多?准确来说是可执行的线程越多,cpu 切换越多,因为操作系统的调度要保证绝对公平,有可执行线程时,一定是要雨露均沾,所以切换次数变多
- 使用异步非阻塞的开发模型,用一个进程或线程接收请求,然后通过 IO 多路复用让进程或线程不阻塞,省去上下文切换的开销
这两个方案,优缺点都很明显,方案1实现简单,但性能不高;方案2性能非常好,但实现起来复杂。有没有介于这两者之间的方案?既要简单,又要性能高,协程就解决了这个问题。
协程是用户视角的一种抽象,操作系统并没有这个概念,其主要思想是在用户态实现调度算法,用少量线程完成大量任务的调度。
协程需要解决线程遇到的几个问题:
- 内存占用要小,且创建开销要小
- 减少上下文切换的开销
第一点好实现,用户态的协程,只是一个数据结构,无需系统调用,而且可以设计的很小,达到 KB 级别。
第二点只能减少上下文切换次数来解决,因为协程的本质还是线程,其切换开销在用户态是无法降低的,只能通过降低切换次数来达到总体上开销的减少,可以有如下手段:
- 让可执行的线程尽量少,这样切换次数必然会少
- 让线程尽可能的处于运行状态,而不是阻塞让出时间片
Goroutine
goroutine 是 golang 实现的协程,其特点是在语言层面就支持,使用起来非常方便,它的核心是MPG调度模型:
- M:内核线程
- P:处理器,用来执行 goroutine,它维护了本地可运行队列
- G:goroutine,代码和数据结构
- S:调度器,维护M和P的信息
除此之外还有一个全局可运行队列。

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

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

3. 当 M0 系统调用结束后,会尝试“偷”一个 P,如果不成功,M0 将 G0 放到全局的 runqueue 中
- P 会定期检查全局 runqueue,保证自己消化完 G 后有事可做,同时也会从其他 P 里“偷” G
从上述看来,MPG 模型似乎只限制了同时运行的线程数,但上下文切换只发生在可运行的线程上,应该是有一定的作用,当然这只是一部分。
golang 在 runtime 层面拦截了可能导致线程阻塞的情况,并针对性优化,他们可分为两类:
- 网络 IO、channel 操作、锁:只阻塞 G,M、P 可用,即线程不会让出时间片
- 系统调用:阻塞 M,P 需要切换,线程会让出时间片
所以综合来看,goroutine 会比线程切换开销少。
总结
从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来,技术的每一步发展都是为了解决实际问题。
搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。
写了一年golang,来聊聊进程、线程与协程的更多相关文章
- python进程.线程和协程的总结
I.进程: II.多线程threading总结 threading用于提供线程相关的操作,线程是应用系统中工作的最小单位(cpu调用的最小单位). Python当前版本的多线程没有实现优先级,线程组, ...
- python系列7进程线程和协程
目录 进程 线程 协程 上下文切换 前言:线程和进程的关系图 由下图可知,在每个应用程序执行的过程中,都会去产生一个主进程和主线程来完成工作,当我们需要并发的执行的时候,就会通过主进程去生成一系列的 ...
- Python学习之路--进程,线程,协程
进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Q ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
- Python—进程、线程、协程
一.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 方法: ...
- Queue、进程、线程、协程
参考博客地址 http://www.cnblogs.com/alex3714/articles/5230609.html 1.python GIL全局解释器锁 python调用的操作系统的原生线程,当 ...
- python中socket、进程、线程、协程、池的创建方式和应用场景
进程 场景 利用多核.高计算型的程序.启动数量有限 进程是计算机中最小的资源分配单位 进程和线程是包含关系 每个进程中都至少有一条线程 可以利用多核,数据隔离 创建 销毁 切换 时间开销都比较大 随着 ...
- 协程、gevent实现异步io、进程、线程、协程对比
异步io的说白了就是遇到io操作的时候,就停下来去做别的事情.io分网络io和磁盘io,网络io比如说打开一个网站获取数据,下载一首歌等等,磁盘io就是把数据存到一个文件里面,写到磁盘上. 从网站上获 ...
- 进程、线程、协程和GIL(二)
上一篇博客讲了进程.线程.协程和GIL的基本概念,这篇我们来说说在以下三点: 1> python中使用threading库来创建线程的两种方式 2> 使用Event对消来判断线程是否已启动 ...
随机推荐
- 庆FastGithub加入.NET Core Community
.NET Core Community .NET Core Community是一个基于并围绕着 .NET 技术栈展开组织和活动的非官方.非盈利性的民间开源社区,提供了很多优秀的 .NET 开源项目. ...
- 用 Java 实现常见的 8 种内部排序算法
一.插入类排序 插入类排序就是在一个有序的序列中,插入一个新的关键字.从而达到新的有序序列.插入排序一般有直接插入排序.折半插入排序和希尔排序. 1. 插入排序 1.1 直接插入排序 /** * 直接 ...
- Java多线程操作同一个对象,线程不安全
Java多线程操作同一个对象 发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱 代码: package multithreading; // Java多线程操作同一个对象 // 买火车票的 ...
- WPF(MVVM) 利用资源字典实现中英文动态切换
1.首先新建两个字典文件en-us.xaml.zh-cn.xaml.定义中英文的字符串在这里面. 2.将两个资源字典添加到App.xaml中,这里注意下,因为两个字典中有同样字符,如果没有动态更改,默 ...
- Python语言系列-04-高阶函数
闭包 #!/usr/bin/env python3 # author:Alnk(李成果) # 什么是闭包 # 1,闭包存在于函数中 # 2,闭包就是内层函数对外层函数(非全局变量)的引用 # 3,最内 ...
- 【笔记】KNN之超参数
超参数 超参数 很多时候,对于算法来说,关于这个传入的参数,传什么样的值是最好的? 这就涉及到了机器学习领域的超参数 超参数简单来说就是在我们运行机器学习之前用来指定的那个参数,就是在算法运行前需要决 ...
- VsCode安装使用教程和插件安装方法
许多渗透方式都python写脚本,比较方便,写一下vscode和插件的安装办法,虽然不是很复杂,但是写一下做一下笔记: Visual Studio Code (简称 VS Code / VSC) 是一 ...
- MATLAB—二维函数可视化
本文主要总结一下MATLAB的一些常用二维绘图指令. 文章目录 一.plot绘图指令 1.离散数据点形设置值 2.连续线型设置值 3.颜色设置值 4.常用属性和属性值 5.例题 二.subplot绘图 ...
- MySQL Mac 终端环境变量配置
MySQL Mac 终端环境变量配置 这里安装的是mysql-8.0.26-macos11-x86_64,M1Mac,原本打算安装arm64版本,但一直安装不了,就装了x86版本 安装完成MySQL之 ...
- NOIP 模拟 $23\; \rm 题$
题解 \(by\;zj\varphi\) 考虑 \(\rm DP\) 设 \(dp_{k}(S)\) 表示前 \(k\) 个人来后 \(S\) 集合中的苹果都存在的概率是否大于 \(0\) 考虑倒着转 ...