多线程模型

多线程模型中内核实现线程与线程之间的调度,通常一个线程是无法从头到尾占用着cpu的,尤其是进行i/o操作时,许多的系统调用都是阻塞的,此时内核保存该线程的上下文,然后挂起该线程。当然更多时候是由于该线程的本次运行时间耗尽,只得被挂起等待cpu的下一次临幸

但是多线程存在两个问题,在线程数量过多时,问题被放大的尤为明显

  • 线程的上下文切换造成的开销。
  • 线程之间对资源的竞争问题。

上下文切换

上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:

  1. 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,
  2. 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复
  3. 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。

这里的切换有一个时间片的概念

时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

在函数调用的时候就已经确定该函数是否会造成阻塞

在I/O密集型运算中,尤其是高并发时。为了保证公平,时间片的分配会越来越小,切换越发频繁。资源也就被浪费在了上下文切换中

而cpu密集型运算中,不需要频繁的切换线程,所以多线程是一个不错的选择

协程

为了解决I/O密集型运算内核在资源调度上的缺陷,所以引入了协程(coroutine)的概念。协程也被称为用户态的线程,内核态的线程调度由内核来完成。而用户态的线程的调度则交给用户来完成,也就是应用程序,也就是我们自己,因此我们可以实现自己的调度算法。更重要的是,即使我们有成千上万的线程,也不用担心线程切换浪费的资源问题了。

上面我们看到要实现线程的调度的关键就是上下文状态的保存。php中,我们通过 Generator对象来实现程序的中断与恢复。Generator 对象在程序中断时会为我们保存中断前的现场。只要有这一点,我们的应用程序就可以自己实现协程了。这里Generator如何保存上下文环境,是否像线程切换一样浪费资源还需要近一步了解。

我们可以用协程实现一个支持高并发的web服务器,如图

我们将在单个进程中同时处理这些并发的请求,从http请求开始接手,一点一点推进,直到response。图中的每一个线程其实就是我们所说的协程,我们要做的就是实现一个调度器,来分配上面每一个线程的运行时间。

具体的实现就参见 鸟哥翻译的这篇文章 在PHP中使用协程实现多任务调度

读完文章我依旧有两个问题

  • 系统调用一定要使用非阻塞版本,否则会把控制权交还给内核,然后阻塞整个进程,有解决办法吗?
  • 对mysql的操作是否属于I/O操作,是否会造成阻塞?

啰嗦一下nginx采用的是异步I/O模型来实现I/O,另外一个说法是,协程解决了异步I/O噩梦般的回调。另外读完鸟哥的那篇文章然后跟着敲了一下代码发现好像并没有什么卵用~