[操作系统] 细说协程 (不看后悔)

前言

近些年, 一些编程语言的新贵Go和Kotlin纷纷引入了协程这个语言特性, 使得协程这个似乎十分陌生的概念开始频繁进入大家的视野, 为了便于理解, 开发者们都把它当作线程的小弟来对待, 即轻量级线程. 可是真要细说起来, 协程其实是很早就出现的一个编程概念, 它的出现甚至是是早于线程的, 但是就编程语言的江湖地位而言, 协程是不如线程的, 所以向线程低头叫爸爸不奇怪. 在我们了解了进程和线程之后, 学习协程将会非常简单.

在使用线程时, 可以最大限度的压榨CPU, 实现并行运行程序, 大大提升效率. 而使用协程, 实际上并没有提升程序的执行效率, 一个线程下的所有协程在任意瞬间都仅仅只能执行一个协程(无论是否多核), 即协程并不能像进程线程一样缩短执行时间. 那么为什么要用到线程? 为什么线程能处理高并发呢?

实际上, 在操作系统的历史上, 协程的出现时间是比线程要早的, 那么协程又是如何提出的? 它又解决了什么问题?

用户级线程

很久很久之前,线程的概念出现了,但操作系统厂商可不能直接就去修改操作系统的内核,因为对他们来说,稳定性是最重要的。贸然把未经验证的东西加入内核,出问题了怎么办?所以想要验证线程的可用性,得另想办法。

在最早期线程还未被操作系统厂商完成那会, 我们使用pthraed线程库, 实际上用的是用户级的线程. 是位于用户空间的,操作系统内核对这个库一无所知,所以从内核的角度看,它还是按正常的方式管理.

也就是说操作系统眼里还是只有进程, 用线程库写的多线程进程,只能一次在一个 CPU 核心上运行. 这其实是用户级线程的一个缺点,这些线程只能占用一个核,所以做不到并行加速,而且由于用户线程的透明性,操作系统是不能主动切换线程的,换句话讲,如果线程 A 正在运行,线程 B 想要运行的话,只能等待 A 主动放弃 CPU. 即使有线程库,用户级线程也做不到像进程那样的轮转调度.

注:对操作系统来说,用户级线程具有不可见性,也称透明性。

虽然不能做到轮转调度,但用户级线程也有他自己的好处——你可以为你的应用程序定制调度算法,毕竟什么时候退出线程你自己说了算。

用户级线程示意图:

在这里插入图片描述

内核级线程

有了用户级线程的铺垫,内核级线程就好讲多了。现在我们知道,许多操作系统都已经支持内核级线程了。为了实现线程,内核里就需要有用来记录系统里所有线程的线程表。当需要创建一个新线程的时候,就需要进行一个系统调用,然后由操作系统进行线程表的更新。当然了,传统的进程表也还是有的。如果操作系统「看得见」线程,有什么好处?

操作系统内核如果知道线程的存在,就可以像调度多个进程一样,把这些线程放在好几个 CPU 核心上,就能做到实际上的并行了. 如果线程可见,那么假如线程 A 阻塞了,与他同属一个进程的线程也不会被阻塞。这是内核级线程的绝对优势.

内核级线程示意图:
在这里插入图片描述

综上, 我们可以看到, 厂商在最初设计时先是设计了运行在用户级的线程(协程), 然后才设计初我们真正意义上的线程. 那么协程使用的意义又在哪里?

线程的缺点

让操作系统进行线程调度,那意味着每次切换线程,就需要「陷入」内核态,而操作系统从用户态到内核态的转变是有开销的(上下文的切换),所以说内核级线程切换的代价要比用户级线程大.而运行在用户空间的用户级线程就没有这个问题. 还有很重要的一点——线程表是存放在操作系统固定的表格空间或者堆栈空间里,所以内核级线程的数量是有限的,扩展性比不上用户级线程.

总结

大家对于协程的理解有很多分歧,但是对我而言,协程其实得分两个阶段来理解

在协程诞生之初,只是用来解决编程中的某些特殊问题的编程组件,它的多任务更像多个函数的组合协作执行,那个时候,协程其实更像是一种具备暂停恢复的函数.但是这种功能似乎并不受欢迎,因此协程在很长一段时间内都是比较小众的.(此时协程和线程关系并不大)

如今它成为底层支持多线程的协作式多任务组件,很好的解决了线程协作的痛点,同时也逐渐变得越来越受欢迎,协程和线程的关系更加亲密,它们似乎也变得更加相似.(如今你可以把协程视作一种轻量级线程)

而协程的发展历程,其实也就是经历了这两个阶段。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值