多进程并发:
当一个进程阻塞时,切换到等待执行的进程把 CPU 利用起来。为了更合理的利用 CPU 资源,内存划分为多块,不同进程使用各自的内存空间互不干扰,CPU 可以在多个进程之间切换执行,让 CPU 的利用率变高。
为了实现 CPU 在多个进程之间切换,需要保存进程的上下文(如程序计数器、栈等),以便下次切换回来可以恢复执行。还需一种调度算法,Linux 中采用了基于时间片和优先级的完全公平调度算法。进程的上下文切换涉及到从用户态->内核态->用户态的过程,并且上下文中包含非常多的数据。进程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,CPU 虽然利用起来了,但如果进程过多,CPU 有很大的一部分都被用来进行进程调度了。
多线程并发:
进程和线程在 Linux 中没有本质区别,最大的不同是进程有自己独立的内存空间,而线程(同一个进程)是共享内存空间。在进程切换时需要转换内存地址空间,而线程切换没有这个动作,所以线程切换比进程切换代价更小。
协程
实际上多线程开发设计会变得更加复杂,要考虑很多同步竞争等问题,如锁、竞争冲突等。
多进程、多线程已经提高了系统的并发能力,但为每个任务都创建一个线程是不现实的,因为会消耗大量的内存。
当程序是 IO 密集型时(如 web 服务器),为了追求高吞吐,有两种思路:
1.为每个请求开一个线程处理,为了降低线程的创建开销,可以使用线程池技术,理论上线程池越大,则吞吐越高,但线程池越大,CPU花在切换上的开销也越大。
2.使用异步非阻塞的开发模型,用一个进程或线程接收请求,然后通过 IO 多路复用让进程或线程不阻塞,省去上下文切换的开销。
方案1实现简单,但性能不高;方案2性能好,但实现起来复杂。
协程是用户视角的一种抽象,操作系统并没有协程的概念。协程运行在线程之上,协程的主要思想是在用户态实现调度算法,用少量线程完成大量任务的调度。
协程需要解决线程遇到的几个问题:
内存占用要小且创建开销要小;用户态的协程,可以设计的很小,可以达到 KB 级别(线程栈空间通常是MB级别);减少上下文切换的开销(让可执行的线程尽量少,这样切换次数必然会少)。
从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来。
协程(协同程序):同一时间只能执行某个协程。开辟多个协程开销不大。协程适合对某任务进行分时处理。
线程:同一时间可以同时执行多个线程。开辟多条线程开销很大。线程适合多任务同时处理。
协程可以让同一个线程执行多个协程任务,降低线程切换的开销。
由于多个线程共享线程栈和寄存器,因此协程切换不涉及到线程栈和寄存器的开销,仅有协程本身一些状态的变更。