服务器的进程、线程和协程_优品建站

服务器的进程、线程和协程

  用Java的人通常写的是“单进程多线程”的程序;而用C++的人,可能写的是“单进程多线程”“多进程单线程”“多进程多线程”的程序(这里主要指Linux系统上的服务器程序)。之所以会有这样的差异,是因为Java程序并不直接运行在Linux系统上,而是运行在JVM之上。而一个JVM实例是一个Linux进程,每一个JVM都是一个独立的“沙盒”,JVM之间相互独立,互不通信。所以Java程序只能在这一个进程里面,开多个线程实现并发。而C++直接运行在Linux系统上,可以直接利用Linux系统提供的强大的进程间通信机制(IPC),很容易创建多个进程,并实现进程间的通信。
  “多进程多线程”是“单进程多线程”和“多进程单线程”的组合体,其原理并没有差异,所以接下来只讨论“单进程多线程”和“多进程单线程”两种编程模型,对比“多进程”和“多线程”的关键差异。
1.为什么要多线程
  对于客户端程序,有UI交互界面,多线程不可避免,这类程序不在讨论之列。本节主要讨论的是服务器端的程序。
  这里所说的“多”线程,是指运行几百个业务线程的服务器程序。如果是4核CPU,运行4个线程,本质上仍是单线程。之所以要开多线程,是因为服务器端的程序往往是I/O密集型的应用。举个极端的例子,假设程序没有任何I/O(磁盘I/O或网络I/O),纯粹的CPU计算,如同一个最简单的、空的死循环,只需要一个线程就可以把一个CPU的核占满。
所以,多线程主要是为了应对I/O密集型的应用。多线程能带来两方面的好处:
  (1)提高CPU利用率。通俗地讲,不能让CPU空闲着。当一个线程发生I/O时,会把该线程从CPU上调度下来,并把其他的线程调度上去,继续计算。
  (2)提高I/O吞吐。典型的场景是,应用程序连接的Redis或者MySQL,它们提供的都是同步接口,一次只能处理一个请求。要想并发,办法是通过连接池和多线程,实现每个线程使用一个连接。好比在客户端和服务器之间开了多条通道,并行传输数据。
  除了多线程,线程间的同步机制也非常复杂,在此只列举线程间常用的同步机制:
  · 锁(悲观锁、乐观锁、互斥锁、读写锁、自旋锁、公平锁、非公平锁等)。
  · Wait与Signal。
  · Condition.
  无论C++开发者在Linux系统中使用的pthread,还是Java开发者使用的JUC库,都有这些基本机制。基于这些基本机制,又可以封装出各式各样的、便于应用层使用的同步机制,比如信号量、Future、线程池,还可以封装出各式各样的线程安全的数据结构,比如阻塞队列、并发HashMap等。
2.多进程
  既然多线程可以实现并发,那为什么要设计多进程呢?因为多线程存在两个问题,一是线程间内存共享,要加线程锁;而加锁后会导致并发效率下降,同时复杂的加锁机制也将增加编码的难度;二是过多的线程造成线程间的上下文切换,导致效率低下。
  在并发编程领域,一直有一个很重要的设计原则:“不要通过共享内存来实现通信,而应通过通信实现共享内存。”这句话不太好理解,换成通俗一点的说法就是:“尽可能通过消息通信,而不是共享内存来实现进程或者线程之间的同步。”
  进程是资源分配的基本单位,进程间不共享资源,通过管道或者Socket方式通信(当然也可以共享内存),这种通信方式天生符合上面的并发设计原则。而对于多线程,大家习惯于共享内存,然后通过加各种锁来实现同步。虽然在多线程领域也有这种思想的实现,比如 Akka 框架,但流行程度仍然不够。
  除锁的问题之外,多进程还带来另外两个好处:一是减少了多线程在不同的CPU核间切换的开销;另外,多进程相互独立,意味着其中一个崩溃后,其他进程可以继续运行,这对程序的可靠性很有帮助。
  多进程模型的典型例子是Nginx。Nginx有一个Master进程,N个Worker进程,每个Worker进程对应一个CPU核,每个进程都是单线程的。Master进程不接收请求,负责管理功能;各个Worker 进程间相互独立,并行地接收客户端的请求,也不需要像多线程那样在不同的 CPU 核间切换。
  有了多进程之后,在每个进程内部,可能是单线程,也可能是多线程,这往往取决于I/O。比如Redis就是单进程单线程的模型(这里说的单线程模型,不是指整个Redis服务器只有一个线程,而是指接收并处理客户端请求的线程只有一个)。之所以单线程可以支持,是因为在请求接收的地方用的是epoll的I/O多路复用,在请求处理的地方又完全是内存操作,没有磁盘或者网络I/O,所以只需单线程就足够了。要利用多核也很简单,开多个Redis实例就可以了。
但对于I/O密集型的应用,要提高I/O效率,则需要下面几种办法:
  (1)异步 I/O。如果客户端、服务器都是自己写的,比如 RPC 调用,则可以把所有的 I/O都异步化(利用epoll或者真正的异步I/O)。异步化之后,请求可以Pipeline处理,就不需要多线程了。但像MySQL的JDBC提供的都是同步接口,不支持I/O异步。
  (2)多线程。I/O不支持异步,就只能开多个线程,每个线程都是同步地调用I/O,实际上是用多线程模拟了异步I/O。典型例子是Web应用服务器调用Redis或MySQL。
  (3)多协程。
3.多协程
  多线程除锁的问题之外,还有一个问题是线程太多,切换的开销很大。虽然线程切换的开销比进程切换的开销小很多,但还是不够。以常用的Tomcat服务器为例,在通常配置的机器上最多也只能开几百个线程。如果再多,则线程切换的开销太大,并发效率反而会下降,这意味着Tomcat最多只能并发地处理几百个请求。但如果是协程的话,可以开几万个!协程相比线程,有两个关键特点:
  · 更好地利用CPU:线程的调度是由操作系统完成的,应用程序干预不了,协程可以由应用程序自己调度。
  · 更好地利用内存:协程的堆栈大小不是固定的,用多少申请多少,内存利用率更高。
  现代的编程语言像Go、Rust,原生就有协程的支持,但偏传统的Java、C++等语言没有原生支持。因此,产生一些第三方的方案,比如Java的Quasar Fiber、微信团队为C++研发的libco等,但普及程度还比较低,开发者还是习惯多线程的开发模型。
  最后,表4-3总结了多线程、多进程和多协程编程模型的对此。
表4-3 多线程、多进程和多协程编程模型的对比

  • Linux服务器的管理和维护建议
    下面这些服务器操作规范和建议初学者可能不容易看懂,但是这些经验之谈对服务器的管理和维护都非常重要。
  • 配置Nginx进程PID存放路径
    Nginx进程作为系统的守护进程运行,我们需要在某文件中保存当前运行程序的主进程号。Nginx支持对它的存放路径进行自定义配置,指令是pid。
  • 如何将Node.js部署到服务器
    在开发完Node.js的项目之后,需要将项目部署到服务器上才能让别人来访问。上线部署的事情一般都由运维人员来操作,但是作为开发人员还是需要知道一些基本的部署知识的。
  • web服务器端文件内容检测及绕过
    这类检测方法相对于上述检测方法来说更为严格。它通过检测文件内容来判断上传文件是否合法。但由于防护手段严格,允许的内容也就更加单一,这里针对图片上传功能进行防护分析。
  • 配置Linux网络时间服务器
    配置Linux异构网络下的NTP服务器,NTP服务的配置文件。
  • 企业Samba服务器实用案例
    企业Samba服务器实用案例:1.企业环境及需求,2.需求分析,3.解决方案。
  • 服务器运维必会知识:利用U盘安装Linux系统
    光盘介质没有U盘携带方便,有的服务器为节省成本甚至没有安装光驱,所以很多管理员习惯做一个U盘的安装盘,随身携带以备不时之需。如果使用U盘作为安装介质,那么U盘需要进行一定的配置,本文我们来学习一下如何使用U盘安装Linux。
  • nginx服务器必会知识点:nginx.conf文件的结构
    我们可以归纳出nginx.conf文件的基本结构为:(“#”后边的内容是笔者添加的注释内容,它们的含义在后文中会给出)。
  • 服务器缓冲I/O和直接I/O
    表4-1列出了缓冲I/O与直接I/O对应的API接口列表,缓冲I/O是C语言提供的库函数,均以f打头;直接I/O是Linux的系统API,但因为操作系统的API也是用C语言编写的,所以导致开发者往往无法区分这两类I/O,但在原理上实际差异很大。
  • mysql_connect函数:打开MySQL服务器的非持久连接
    该函数将打开一个非持久的到MySQL服务器的连接。如果函数执行成功,将返回一个MySQL服务器的连接标识符,执行失败则返回FALSE。
  • 网站制作 服务

    免费网站制作报价,免费优化,1对1服务,个性化定制服务

    pc和wap网站制作

    多年建站经验,上千个成功案例,
    为您提供一站式服务

    网站维护改版

    大厂经验工程师对现有网站进行
    改版,修复,维护。

    小程序制作

    微信小程序,支付宝小程序,
    百度小程序

    响应式网页设计

    响应式网页设计可以与多种设备兼容,
    如智能手机,平板电脑和PC