在现在大数据、高并发,到处都充斥着流量的互联网时代,能不能应对高并发俨然已经发展成一个衡量服务端架构是否合格的标准,作为程序媛我们思考如何利用语言在代码层面最优设计能应对并发的程序去并行处理任务。在程序中对于任务并行处理一般趋于使用:进程、线程,以及另外一种:协程。支持协程的语言有很多,比如:C/C++、Ruby、 Python(2.5+)、Golang等等,它们有些是本身语言支持协程,有些则是需要引入第三方包来使用。不过,我们主要来学习一下Golang这门语言(简称Go),它是如何理解以及实现协程的。
一、进程、线程和协程的前世今生
都知道一台计算机的核心是CPU,它承担着所有的运算。而计算机承载的操作系统(内核)则是负责所有任务的处理和调度CPU以及资源的分配。如果用人类来比喻,大脑是CPU,思维则是操作系统(内核)。
进程
最早的计算机每次只能运行一个程序,如果还有其他程序需要执行则要排队等待。后来CPU运算能力提高了,这种方式过于原始有些浪费性能,于是尝试让多个程序可以并行执行,但是这样面临一个新的问题:跑在同一个CPU中的程序都会使用计算机资源,那程序的运行状态和数据怎么保障?进程。
1 | 进程是内核资源管理分配的最小单位,每个进程都有独立的虚拟地址空间。内核中的每个程序都运行在独立进程的上下文中,上下文是由程序正常运行需要的一系列参数组成,参数包括存储器中的代码和数据,寄存器中的内容以及进程打开的文件描述符(文件句柄)等。可以把上下文通俗理解为:`环境`。 |
如果程序在运行过程中需要进行IO操作,IO操作阻塞了程序后面的计算,这时候CPU属于空闲状态,那内核会把CPU切换到其他进程去处理。不过当进程数量变高以后,计算机的大部分资源都被进程切换这个操作消耗掉了。为什么说进程切换操作消耗资源代价比较高?
1 | 所谓进程切换其实就是上下文切换,需要切换新的页表并加载新的虚拟地址空间、切换内核栈以及硬件上下文等。只要发生进程切换操作就得反复进入内核,加载切换一系列状态。 |
线程
为了减少这种开销,线程应运而生。
1 | 线程是内核调度CPU执行的最小单位,线程是运行在进程上下文的逻辑流,线程是具体执行程序的单位。一个进程至少包含一个主线程(可以拥有多个子线程),但是一个线程只能存在于一个进程中。 |
线程切换相比进程切换开销就小了很多,线程切换只需要把寄存器刷新即可。
协程
后面程序媛们发现线程这样还是有性能瓶颈(IO阻塞),无论是进程还是线程因为涉及到大量的计算机资源,所以都是由内核调度管理。能不能开发一种由代码控制的线程呢?这就是协程。
