服务器的两种并发原理

众所周知,如今的服务器能够处理多个socket链接,背后并发的实现主要有两种途径。php

  1. 多线程同步阻塞
  2. I/O多路复用

socket的创建

聊到socket,就不得不提到socket的创建的流程。祭出经典的老图:
jpgnode

服务器依次使用socket,bind,listen以后就会监听对应的地址,此时accept会一直阻塞直到有链接创建,若是客户端和服务器创建了链接,那么accept就会返回一个链接句柄,能够对链接进行读数据或者写数据。nginx

同步阻塞

那么问题来了,服务器若是不作特殊处理的话,一次只能处理一个链接,新的链接来是须要等待上一个链接结束才能链接成功,这就是最开始的服务器同步阻塞方法。redis

同步阻塞:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操做成功则进程获取到数据。segmentfault

多线程并发

可能之前的拥有的电脑人很少,这种方式一次只能链接一个倒也没有问题,以后访问的人开始多起来,设计者以为这样下去不行,就设计了多线程同步阻塞的方法。每次accept得到一个句柄,就建立一个线程去处理链接,这下子就能同时处理多个链接呢。这就是经典的多线程同步阻塞的方法。服务器

典型的多线程(进程)并发模型就是cgi网络

特色

服务器和客户端之间的并发,有如下特色:多线程

  1. 外部链接不少,但不少链接是不活跃的链接,典型的如聊天的im系统。
  2. 少许的CPU消耗。
  3. 大部分的时间耗费在I/O阻塞和其余网络服务。
  4. 外部网络不稳定,客户端收发数据慢不少。
  5. 对业务的请求处理很快,大部分时候毫秒级就能够完成。

问题

根据以上特色,咱们能够得知服务器有如下问题:
若是采用多线程同步阻塞,1个tcp链接须要创建1个线程,10k个链接须要创建10k个链接,然而大部分链接是不活跃,即使是须要处理业务逻辑,也能够快速返回结果,大部分时间也是处于I/O阻塞或网络等待。这就使得多个线程的建立很耗费资源,且线程的切换也是极其耗费CPU,这就极可能致使了CPU处理业务的消耗的资源很少,可是却花了不少资源在进程切换上面。并发

I/O多路复用

多线程并发的问题是大部分socket都是闲置的状态或者是处于IO阻塞的状态,那能不能把阻塞的socket先扔到一边去处理其余事情,来避免等待所带来的资源耗费,也就是非阻塞IO的概念。异步

非阻塞IO

当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,而是马上返回一个error。从用户进程角度讲 ,它发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。用户进程判断结果是一个error时,它就知道数据尚未准备好,因而它能够再次发送read操做。一旦kernel中的数据准备好了,而且又再次收到了用户进程的system call,那么它立刻就将数据拷贝到了用户内存,而后返回。

所以:使用非阻塞IO是须要不断轮询IO数据是否好了。

IO多路复用原理就是不断轮询多个socket,当其中的某个socket准备好了数据就返回,不然整个进程继续阻塞,就可让一个进程在不太耗费资源的状况下处理多个链接,可是这个轮询的操做是交给内核态去完成,也就避免了内核态和用户态的切换的问题。

而目前的实现方法有select, poll, epoll,其中epoll的性能最好,用的也是最普遍。

优势

  1. 避免了建立多个线程所耗费的资源以及时间。
  2. 对socket的轮询是内核态的完成,不须要像多线程那样切换须要耗费资源。

而epoll的实现能够作到性能几乎不受链接数(单单是链接而没有其余的操做)的影响。

固然多路复用IO也有本身的问题,也就是自己不支持多核的使用,须要另外解决多核的利用。

其中使用enroll的成熟程序有nginx,redis,nodej等。

服务器的发展

根据知乎大佬的介绍,服务器通过发展能够分为两阶段:

第一代服务器模型

把传输层的tcp并发的链接放到IO多路复用去处理,应用层继续使用多线程并发模型去作。这样就能够大幅度减小线程的建立切换的资源耗费。
如:nginx + php-fpm(实际上是php-fpm是多进程)

第二代服务器模型

第二代服务器模型是把应用层也使用IO多路复用去处理,减小应用层的等待外部接口调用阻塞等待,通常是大厂大流量并发须要用到。
如:

  1. nodejs的异步回调
  2. Go的goroutine

参考资料:
[1]:许怀远的知乎回答
[2]:Linux IO模式及 select、poll、epoll详解

相关文章
相关标签/搜索