2.Epoll(新版的Apache的event MPM,Nginx等支持)
  Epoll是Linux2.6开始正式支持的I/O多路复用,我们可以理解为它是对select/poll的改进。首先,我们同样将我们关注的socket文件描述符集合告诉给内核,同时,给它们注册”回调函数“,如果某个socket文件准备好了,通过回调函数通知我们。于是,我们不需要专门去轮询整个全量的socket文件描述符集合,直接可以得到已经可操作的socket文件描述符。那么,那些大部分”空闲“的描述符,我们不遍历了。即使我们监控的socket文件描述越来越多,我们轮询的也只是”活跃可操作“的socket文件描述符。
  其实,有一种极端点的场景,是我们全部文件描述符几乎都是”活跃“的,这样反而导致了大量回调函数的执行,又增加了CPU的开销。但是,Web服务的真实场景,绝大部分时候,都是连接集合中都存在很多”空闲“连接。
  3.线程/进程的创建销毁和上下文切换
  通常,Apache某一个时间内,是一个进程/线程服务于一个连接。于是,Apache有很多的进程/线程,服务于很多的连接。Web服务在高峰期,会建立很多的进程/线程,也带来很多的上下文切换开销。而Nginx,它通常只有1个master主进程和几个worker子进程,然后,1个worker进程服务很多个连接,进而节省了CPU的上下文切换开销。

  两种模式虽然不同,但实际上不能直接出分好坏,综合来说,各有各自的优势,不妄议了哈。
  4.多线程下的锁对CPU的开销
  Apache中的worker和event模式,都有采用多线程。多线程因为共享父进程的内存空间,在访问共享数据的时候,会产生竞争,也是线程安全问题。因此通常会引入锁(Linux下比较常用的线程相关的锁有互斥量metux,读写锁rwlock等),成功获取锁的线程可以继续执行,获取失败的通常选择阻塞等待。引入锁的机制,程序的复杂度往往增加不少,同时还有线程“死锁”或者“饿死”的风险(多进程在访问进程间共享资源的时候,也有同样的问题)。
  死锁现象(两个线程彼此锁住对方想要获取的资源,相互阻塞等待,永远无法达不到满足条件):

  饿死现象(某个线程,一直获取不到它想要锁资源,永远无法执行下一步):

  为了避免这些锁导致的问题,不得不加大程序的复杂度,解决方案一般有:
  (1)对资源的加锁,根据约定好的顺序,大家都先对共享资源X加锁,加锁成功之后才能加锁共享资源Y。
  (2)如果线程占有资源X,却加锁资源Y失败,则放弃加锁,同时也释放掉之前占有的资源X。
  在使用PHP的时候,在Apache的worker和event模式下,也必须兼容线程安全。通常,新版本的PHP官方库是没有线程安全方面的问题,需要关注的是第三方扩展。PHP实现线程安全,不是通过锁的方式实现的。而是为每个线程独立申请一份全局变量的副本,相当于线程的私人内存空间,但是这样做相对消耗多一些内存。不过,这样的好处,是不需要引入复杂的锁机制实现,也避免了锁机制对CPU的开销。
  这里顺便提到一下,经常和Nginx搭配工作的PHP-FPM(FastCGI)使用的是多进程,因此不会有线程安全的问题。

  五、小结
  可能有些同学看完之后,会得出结论,Nginx+PHP-FPM的工作方式,似乎是节省系统资源的Web系统工作方式。某种程度上说,的确是可以这么说的,但是Web系统的搭建,需要从实际业务应用的角度出发,具体问题需要具体分析,寻求合适的技术方案。
  Web服务的不断演变和发展,努力地追求用尽可能少的系统资源,来支撑更多的用户请求,这是一条波澜壮阔的前进之路。这些技术方案,汇聚了很多值得学习和借鉴的解决问题的思路。