让代码运行得更快:深入理解进程、线程和协程
什么是执行体
在深入探讨进程、线程和协程之前,我想先介绍下执行体这个概念。
执行体这个词语是我从七牛云创始人许式伟大佬的专栏中学到的,它代表操作系统中程序执行的载体,涉及到计算资源的分配、访问权限的控制等方面。我们经常提到的进程、线程和协程就是三种不同的执行体。
我们在谈论具体的某种执行体前,首先需要认识到各种执行体出现的根本原因,那就是提高CPU和内存的利用效率。如果我们不定义相应的执行体,CPU和内存就会被浪费掉,这对于计算机的性能是极大的浪费。比如说,我们正在看电影,但是突然想查看一下邮件,如果我们不将电影播放和邮件检查划分为不同的任务,那么我们就需要暂停电影,然后去查看邮件,然而对视频进行解码和从网络拉取邮件是可以并行的操作,这就浪费了CPU的计算能力。因此,合理的划分执行体是提高计算机性能的一个重要手段。
进程
进程是操作系统进行资源分配和调度的基本单位,它是程序运行的载体。
那么,我们为什么需要进程呢?
其实,这主要是为了满足操作系统的多任务需求。我们在使用电脑时,常常需要同时运行多个程序,比如上网、听音乐、写文档等等,这就需要操作系统能够同时处理多个任务,而进程就是实现这个功能的基础。
进程的创建在不同的操作系统中有所不同。在Unix/Linux/Mac中,我们主要使用fork函数来创建进程。而在Windows中,我们使用CreateProcess函数。这两个函数虽然在实现方式上有所不同,但是他们的目的都是创建一个新的进程。
进程有两个重要的特征。
一是内存地址空间隔离,也就是说,每个进程都有自己独立的内存空间,这保证了进程之间不会相互干扰。
二是操作系统内核调度,也就是说,进程的运行是由操作系统内核来调度的,它决定了哪个进程先运行,哪个进程后运行,内核可以在一定程度上保障资源在不同进程之间的合理分配。
线程
线程是比进程更小的执行单位,它是进程内部的独立执行路径。
我们为什么需要线程呢?
这是因为我们在开发一个软件时,常常需要在软件内部进行多任务处理,比如我们在编写一个文档时,可能需要同时进行拼写检查、保存文档等任务,这就需要我们在软件内部创建多个线程来处理这些任务。
线程的特征和进程有些相似,但也有一些不同。
线程同样是由操作系统内核调度的,但是线程之间是共享进程内存地址空间的,这意味着同一个进程内的线程可以访问到相同的数据。
此外,每个线程都有自己的栈,栈的大小在不同的操作系统中有所不同,比如在Linux中,默认线程栈是8M,而在Windows中,默认线程栈是1M。
协程
协程是一种比线程更轻量级的执行单位,它主要用于处理高性能的网络服务需求。
为什么我们需要协程呢?
这是因为在处理高性能网络服务时,我们常常需要使用异步IO,但是异步IO会导致大量的回调逻辑碎片化,这对于我们的编程是非常不利的。因此,我们需要回归到同步IO编程模式,而协程就是实现这个目标的一个重要工具。
协程的特征有三点。
一是它同样共享进程内存地址空间;
二是它由用户程序调度,这意味着我们可以在程序中自由地创建和销毁协程;
三是它无栈或者有较小的栈,这使得我们可以创建大量的协程而不用担心内存消耗。
此外,协程切换的时间成本也比较低。这是因为协程切换是在用户态进行,避免了频繁的内核态和用户态切换,节省了大量的CPU指令。同时,由于协程的上下文结构简单,保存和恢复的开销小,而且由程序自身控制切换时机,可以避免在关键路径上进行切换,这些都大大降低了切换的时间成本。
目前对协程支持的比较好的语言有Go和Erlang,其它语言如C#、Java、Javascript等对协程也有一定的支持。
总结起来,进程、线程和协程都是为了提高CPU和内存的利用率,满足多任务处理的需求,他们在创建方式、特征和应用场景上各有不同,但都是为了让我们的计算机运行得更高效。希望通过这篇文章,大家对计算机的执行体有了更深入的理解。