写了一年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 服务器、网关等),为了追求高吞吐,有两种思路:

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

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

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

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

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

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

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

  • 内存占用要小,且创建开销要小
  • 减少上下文切换的开销

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

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

  1. 让可执行的线程尽量少,这样切换次数必然会少
  2. 让线程尽可能的处于运行状态,而不是阻塞让出时间片

Goroutine

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

  • M:内核线程
  • P:处理器,用来执行 goroutine,它维护了本地可运行队列
  • G:goroutine,代码和数据结构
  • S:调度器,维护M和P的信息

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

写了一年golang,来聊聊进程、线程与协程
  1. 在 golang 中使用 go 关键字启动一个 goroutine,它将会被挂到 P 的 runqueue 中,等待被调度

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

写了一年golang,来聊聊进程、线程与协程
3. 当 M0 系统调用结束后,会尝试”偷”一个 P,如果不成功,M0 将 G0 放到全局的 runqueue 中
  1. P 会定期检查全局 runqueue,保证自己消化完 G 后有事可做,同时也会从其他 P 里”偷” G

从上述看来,MPG 模型似乎只限制了同时运行的线程数,但上下文切换只发生在可运行的线程上,应该是有一定的作用,当然这只是一部分。

golang 在 runtime 层面拦截了可能导致线程阻塞的情况,并针对性优化,他们可分为两类:

  • 网络 IO、channel 操作、锁:只阻塞 G,M、P 可用,即线程不会让出时间片
  • 系统调用:阻塞 M,P 需要切换,线程会让出时间片

所以综合来看,goroutine 会比线程切换开销少。

总结

从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来,技术的每一步发展都是为了解决实际问题。

搜索关注微信公众号”捉虫大师”,后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。

Original: https://www.cnblogs.com/zhuochongdashi/p/15245612.html
Author: 捉虫大师
Title: 写了一年golang,来聊聊进程、线程与协程

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/516610/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • golang 实现一个简单的命令行进度条

    由于有时候跑脚本几个小时看不到进度,所以想着写一个简单的命令行的进度条。类似下面这样的 其中的原理主要是\r回车符(将光标移动到行首)。这样的话就可以重新打印一行以覆盖之前的那一行…

    Go语言 2023年5月25日
    044
  • 基于Go语言实现好用的HTTP接口请求requests

    使用Go自带的net/http库可以发送各种HTTP请求。然而各种类型请求发送方式有点不太一致,这里参考Python requests库的使用方式,简单封装了一下。代码如下: 文件…

    Go语言 2023年5月29日
    055
  • gofs使用教程-基于golang的开源跨平台文件同步工具

    gofs是基于golang开发的一款开箱即用的跨平台文件同步工具,支持在本地磁盘之间同步、从远程服务器同步变更到本地、将本地文件变更推送到远程服务器三种模式。开源地址如下:Gith…

    Go语言 2023年5月25日
    050
  • [grpc快速入门] 一 grpc生成与调用

    下载通用编译器 地址:https://github.com/protocolbuffers/protobuf/releases选择对应的版本,解压后将文件夹下bin目录配置到环境变…

    Go语言 2023年5月25日
    074
  • 【Go实战基础】数组实战,程序员的基本功

    数组实战,程序员的基本功。 实战需求: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 实战思路: 1、先声明…

    Go语言 2023年5月25日
    029
  • GO的URL合法性检查

    原文连接:https://www.zhoubotong.site/post/67.html Go 标准库的net/url包提供的两个函可以直接检查URL合法性,不需要手动去正则匹配…

    Go语言 2023年5月25日
    074
  • Go语言之接口

    接口就是一系列方法的集合(规范行为) 在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为,规范子类对象的行为。 在 Go 语言中的接口是非侵入式接口(接口没了,不影响代码…

    Go语言 2023年5月25日
    053
  • Excelize 2.5.0 正式发布,这些新增功能值得关注

    Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Mic…

    Go语言 2023年5月25日
    056
  • 使用Go http重试请求

    原文连接:https://www.zhoubotong.site/post/78.html开发中对于http请求是经常遇到,一般可能网络延迟或接口返回超时,对于发起客户端的请求, …

    Go语言 2023年5月25日
    047
  • 踩了个DNS解析的坑,但我还是没想通

    hello大家好,我是小楼。 最近踩了个DNS解析的小坑,虽然问题解决了,但排查过程比较曲折,最后还是有一点没有想通,整个过程分享给大家。 背景 最近负责的服务要置换机器。置换机器…

    Go语言 2023年5月25日
    0100
  • 支持首次触发的 Go Ticker

    促使我写这篇文章主要是在写一个关于虚拟货币账户监控的项目时使用 Ticker 的问题。 Ticker 的问题 如果用过 Ticker 的朋友会知道,创建 Ticker 后并不会马上…

    Go语言 2023年5月25日
    060
  • go-micro使用Consul做服务发现的方法和原理

    go-micro v4默认使用mdns做服务发现。不过也支持采用其它的服务发现中间件,因为多年来一直使用Consul做服务发现,为了方便和其它服务集成,所以还是选择了Consul。…

    Go语言 2023年5月25日
    080
  • day2-变量与数据类型

    变量 概念:程序的基本组成单位 定义: 指定变量类型 根据值自行判断变量类型(类型推导) 省略var,定义赋值 var i int var i = 10 i, j := 20, 1…

    Go语言 2023年5月25日
    051
  • Go 语言实现 gRPC 的发布订阅模式,REST 接口和超时控制

    在多个平台的阅读量都创了新高,在 oschina 更是获得了首页推荐,阅读量到了 1w+,这已经是我单篇阅读的高峰了。 看来只要用心写还是有收获的。 这篇咱们还是从实战出发,主要介…

    Go语言 2023年5月25日
    062
  • 并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13

    如果说Go lang是静态语言中的皇冠,那么,Goroutine就是并发编程方式中的钻石。Goroutine是Go语言设计体系中最核心的精华,它非常轻量,一个 Goroutine …

    Go语言 2023年5月25日
    048
  • 第十五章:指针类型

    本篇翻译自《Practical Go Lessons》 Chapter 15: Pointer type 1 你将在本章将学到什么? 什么是指针? 什么时指针类型? 如何去创建并使…

    Go语言 2023年5月25日
    053
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球