多线程模型
多线程模型中内核实现线程与线程之间的调度,通常一个线程是无法从头到尾占用着cpu的,尤其是进行i/o操作时,许多的系统调用都是阻塞的,此时内核保存该线程的上下文,然后挂起该线程。当然更多时候是由于该线程的本次运行时间耗尽,只得被挂起等待cpu的下一次临幸。
但是多线程存在两个问题,在线程数量过多时,问题被放大的尤为明显
线程的上下文切换造成的开销。
线程之间对资源的竞争问题。
上下文切换
上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:
挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,
在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复
跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
这里的切换有一个时间片的概念
时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。
在函数调用的时候就已经确定该函数是否会造成阻塞
在I/O密集型运算中,尤其是高并发时。为了保证公平,时间片的分配会越来越小,切换越发频繁。资源也就被浪费在了上下文切换中
而cpu密集型运算中,不需要频繁的切换线程,所以多线程是一个不错的选择
协程
为了解决I/O密集型运算内核在资源调度上的缺陷,所以引入了协程(coroutine)的概念。协程也被称为用户态的线程,内核态的线程调度由内核来完成。而用户态的线程的调度则交给用户来完成,也就是应用程序,也就是我们自己。我们可以实现自己的调度算法。更重要的是,即使我们有成千上万的线程,也不用担心线程切换浪费的资源问题了。
上面我们看到要实现线程的调度的关键就是上下文状态的保存。php中,我们通过 Generator对象来实现程序的中断与恢复。Generator 对象在程序中断时会为我们保存中断前的现场。只要有这一点,我们的应用程序就可以自己实现协程了。这里Generator如何保存上下文环境,是否像线程切换一样浪费资源还需要近一步了解。
我们可以用协程实现一个支持高并发的web服务器,如图
我们将在单个进程中同时处理这些并发的请求,从http请求开始接手,一点一点推进,直到response。图中的每一个线程其实就是我们所说的协程,我们要做的就是实现一个调度器,来分配上面每一个线程的运行时间。
具体的实现就参见 鸟哥翻译的这篇文章 在PHP中使用协程实现多任务调度
本作品采用《CC 协议》,转载必须注明作者和本文链接