PHP

PHP进程与线程

2019-04-13  本文已影响64人  JunChow520

PHP是单线程,还是多线程的呢?
PHP是多进程,还是多线程的呢?
...
解决这些问题,首先必须先了解线程和进程。

备注:进程和线程的都是比较抽象的计算机概念,可参阅《漫画进程与线程》建立一个初步的认知。

计算机资源

经典的冯洛伊曼结构中把计算机系统抽象成“CPU+存储器+IO”三部分,计算机资源无外乎两种:

冯洛伊曼体系结构

什么是虚拟内存机制,有什么作用呢?

多任务

现代的操作系统如Windows、Linux、UNIX、MacOS等都是支持“多任务”的操作系统,那么什么是多任务呢?

简单来说,就是操作系统可以同时运行多个任务。现在多核CPU已经普及,但在早期单核时代也是可以执行多任务的。由于CPU执行代码都是顺序执行的,那么单核CPU是如何执行多任务的呢?

操作系统轮流让各个让任务交替执行,表面上看每个任务都是交替执行的,但是由于CPU的执行速度很快,在人类的感知中就好像是所有任务都在同时执行一样。

真正的并行执行多任务只能在多核CPU上实现,但是由于任务数据远远超过CPU的核心数量。所以,操作系统也会自动把多个任务轮流调度到每个核心上去执行。

对于操作系统来说,一个任务就是一个进程。由于每个进程至少要做一件事儿,但有些进程不止同时只做一件事儿。当在一个进程中需要同时做多件事情时就需要同时运行多个“子任务”,我们把进程内这些“子任务”称为线程。所以,一个进程至少有一个线程。

在实际编程中实现多任务的方式主要有三种:

同时执行多个任务通常各个任务之间并不是没有关系的,而是需要相互通信和协调。因此,多进程和多线程的程序的复杂度要远远高于单进程单线程的程序。

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。多进程和多线程的程序涉及到同步、数据共享等问题,编写起来比单线程更为复杂。

任务调度

大部分操作系统如Windows、Linux的任务调度是采用时间轮转的抢占式调度方式,也就是说,一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行的状态叫做运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态并等待下一个属于它的时间片的到来。这样往复循环每个任务都得以执行,由于CPU的执行效率很高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在同时运行(并发)。

操作系统中的任务调度

进程Process

什么是进程?

进程是操作系统结构的基础,Multics的设计者在20世纪60年代首次使用了“进程”这个技术词语,它比“作业”更加通用一些。

进程是对计算机的一种抽象,进程表示一个逻辑控制流,也就是一种计算过程,它造成了一个假象,好像这个进程一直是独占CPU资源的。另外,进程拥有一个独立的虚拟内存地址空间,它也造成了一个假象,好像这个进程一直在独占存储资源。

进程包含指令集和系统资源集

简单来说,进程是具有一定独立功能的程序,在关于某个数据集合上的一次运行活动。换言之,进程是一个程序在一个数据集上的一次动态执行过程,是系统进行资源分配和调度的一个独立单位。

进程一般由程序、数据集、进程控制块三部分组成:

也可以把进程当作由一组元素组成的实体,进程的两个基本元素是:

假如CPU开始执行这个程序代码,把这个执行实体称为进程。在进程执行时,任意给定时间,进程都可以唯一的被表征为以下元素:

进程元素

进程控制块PCB

进程控制块

进程的构成元素会被存放在一个叫做“进程控制块”(PCB,Processing Control Block)的数据结构中,进程控制块是操作系统能够支持多进程和多任务的结构。

当操作系统执行进程切换时,会执行两步操作:

不管是中断还是执行,进程控制块中的程序计数器、上下文数据、进程状态都会发生变化。

线程Thread

为什么会存在线程这样的概念?它解决了什么样的问题呢?

在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也就是程序执行的最小单位。任务调度采用时间片轮转的抢占式调度方式,由于进程是任务调度的最小单位,每个进程由各自独立的一块内存,使得各个进程之间内存地址相互隔离。

早期没有线程时候的进程

随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销越来越大,对多个任务之间上下问切换的效率要求越来越高,已经无法满足越来越复杂的程序的要求了。于是就出现了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。

线程

一个进程可以由一个或多个线程,各个线程之间共享程序的内存空间,也就是所在进程的内存空间。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间和多个线程组成。

理论上来说,在Linux内核中是没有线程这个概念的,只有内核调度实体(Kernal Scheduling Entry,KSE)这个概念。Linux的线程本质上是一种轻量级的进程(Light Weight Process, LWP),是通过clone克隆系统调用来创建的。由于进程是一种KSE,线程也是一种KSE。所以线程是操作系统调度的最小单元。

进程是程序执行时的一个实例,是程序执行到某种程度的数据结构的汇集。从内核的角度来看,进程的就是分配系统资源的基本单位。

线程是进程的一个执行流,是CPU调度和分派的基本单位。线程是比进程更小的能独立运行的基本单位。简单来说,进程是资源分配的最小单位,线程是程序执行的最小单位。

一个进程会由多个线程组成,线程与同属一个进程的其它线程共享进程所拥有的全部资源。

进程与线程

进程有两个特性部分:资源所有权和调度执行

进程的这两个特性部分是可以分开的,分开后拥有资源所有权的通常称为进程,拥有执行代码的可分派部分的被称之为线程或轻量级进程。

线程Thread有“执行的线索”的意思在其中,而进程Process在多线程环境中被定义为资源所有者,还会存储进程的进程控制块PCB

线程的结构

线程的结构与进程不同,每个线程包含四部分:

每个进程都有一个进程控制块和用户地址空间,每个线程都有一个独立的栈和独立的控制块,以及一个独立执行上下文。

进程模型

线程的执行过程

线程的执行过程与进程不同,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。线程不能够独立执行,必须依存于进程之中,由于进程提供多个线程执行控制。从逻辑上看,多线程的意义在于一个进程中,有多个执行部分可以同时执行。此时,进程本身不是基本运行单位,而实线程的容器。

线程于进程对比而言,优势在于快,不管是线程的创建还是终止,不管是线程间的切换还是线程间共享数据或通信,它的速度于进程相比都有较大的优势。

单线程与多线程

单线程与多线程

进程与线程的关系

并发与并行

简单来说,并发和并行的区别在于一个CPU处理器同时处理多个任务和多个CPU处理器或者是多核处理器同时处理多个不同的任务,并发是逻辑上同时发生,而并行则是物理上的同时发生。

高并发

可参见《高并发原理》,后续待补。

多进程与多线程的关系

多进程与多线程

使用多进程的优势

使用多线程得优势

使用多线程存在的问题

并发模型

C10K(Client 10000)问题 - 如何单服同时服务1w个客户端?

由于早期服务器是基于进程/线程模型,每新来一个连接就分配一个进程/线程去处理这个连接,而进程/线程在操作系统中会占用一定的资源的。由于硬件的限制,进程/线程的创建是有瓶颈的。另外,进程/线程的上下文切换也是有成本的,每次调度器调度线程,操作系统都要把线程的各种必要信息如程序计数器、堆栈、寄存器、状态等保存起来。

由于CPU的运算速度远快于I/O操作,互联网应用如Web都是I/O密集型而非计算密集型的。I/O密集型是指计算机CPU大量的时间耗费在等待数据的输入和输出上,而不是计算上。当CPU大部分时间都在等待I/O的时候,大部分计算资源是被浪费掉了的。显然,简单粗暴地开一个进程/线程去处理一个连接时不够的,为了达到高并发,需要重点思考的是I/O策略(模型),在同样的硬件条件下不同的设计会产生很大的差异。

PHP并发模型

PHP并发模型可分为多进程模型和多线程模型,那么PHP使用的哪一种呢?答案是都支持,也就是说PHP支持多线程的模型,在多线程情况下,通常要解决的问题是资源共享和隔离,而PHP自身就是线程安全的。

那么到底是哪一种呢?具体来说就需要看PHP所使用的是那个SAPI,例如在Apache中就可能使用多线程模型也可能使用多进程模型,在PHP-FPM中使用的就是多进程模型。

目前比较推荐的方式是使用PHP-FPM的模型,因为这个模型对PHP来说有诸多优势:

多进程与多线程各具优势,例如HHVM选择的就是多线程模型,多线程模型最大的好处是数据共享和通信方便,因为在同一个进程空间内,可以直接使用指针。在PHP的opcode cache工具中apc和opcache等使用的是共享内存来共享opcode,而在HHVM中则不需要走共享内存。共享内存有个问题是存储复杂的数据结构不方便,因为指针的问题,多线程情况下C/C++中的数据结构是可以共享的。这对效率提升也是有帮助的。

多进程和多项还有一个明显的模型区别是在处理请求时的逻辑上

多线程的PHP

PHP从代码级别来讲是不支持多线程操作的,不能像Java、C#等语言一样编写多线程代码。多线程只是代码运行时在同一时刻同时执行多个线程任务,来提高服务器CPU的利用率。PHP是可以以多进程方式执行,例如PHP的进程管理工具PHP-FPM的进程管理机制就是采用了多进程单线程的方式,有效提高了并发访问的响应效率。

PHP从设计之初到流行起来都没有出现明显需要多线程才能解决问题的需求,某些需要多线程的地方也有相应的解决方案和替代方案。而且多线程并不总是比单线程具有更多优势,另外多线程可能会引入其它问题,例如多个线程同时调用一个类中的同一个方法时,可能出现死锁的情况。

简单来说,对于一个客户端的一个页面请求处理的PHP是单线程的,这样做的好处是可以自上而下的编写和理解代码中的业务逻辑。但在PHP是可以同时开启多个线程来处理多个客户端请求的同一个PHP脚本,所以PHP也可以看成是多线程的。

虽然每个PHP脚本的执行是单线程的,但对于Web服务器组件如Apache/Nginx/PHP-FPM是多线程的,因为客户端每次对某个PHP脚本的发起请求时,Web服务器都会创建一个新的进程/线程,用来执行对应的PHP脚本。也就是说,对于一个客户端请求来说,PHP是单线程的,但多个请求间是并发的。

简单来说,PHP本身是不支持多线程,但是Web服务器是支持多线程的,利用Web服务器本身的多线程来处理,从Web服务器多次调用实现多线程的程序。也就是说,可以多人同时访问,这也就是在PHP中实现多线程的基础。

PHP线程安全

如何选择PHP的版本,TS or NTS?

由于Linux/UNIX系统采用多进程的工作方式,而Windows系统采用多线程的工作方式。如果在Windows的IIS下以CGI方式运行PHP会非常慢,因为CGI模式是建立在多进程的基础之上的,而非多线程。所以,在Windows IIS中会把PHP配置成以ISAPI的方式来运行,ISAPI是多线程方式。但存在要给问题,很多PHP扩展时以Linux/UNIX的多进程思想开发的,这些扩展在ISAPI方式运行时出错并搞垮IIS。因此,在IIS下CGI模式才是PHP运行的最安全方式,但CGI对于每个HTTP请求都需要重新记载和卸载整个PHP环境,其消耗是巨大的。

为了兼顾IIS下PHP的效率和安全,Microsoft提出了FastCGI的解决方案,FastCGI可以让PHP的进程重复利用,而不是为每个新请求就重开一个进程。同时FastCGI也可以运行多个进程同时执行,这样即解决了CGI进程模式消耗太大的问题,又利用上了CGI进程模式不存在线程安全问题的优点。

因此,如果PHP使用ISAPI的方式运行就必修使用线程安全(Thread Safe, TS)的版本,如果使用FastCGI模式就没有必要使用线程安全检查,可采用非线程安全(None Thread Safe, NTS)版本以提高效率。

ZTS是什么?

PHP的SAPI多数是单线程环境,比如CGI、CLI、FPM,每个进程只启动一个主线程,这种模式下是不存在线程安全问题的。但也又多线程的环境,如Apache,在这种情况下就需要考虑线程安全问题。因为PHP中又很多全局变量,如EG、CG。如果多个线程共享同一个变量将会发生冲突,所以PHP为多线程的应用模型提供了一个安全机制 - Zend线程安全(Zend Thread Safe,ZTS)。

PHP专门为解决线程安全问题抽象出一个线程安全资源管理器(Thread Safe Resource Manager, TSRM),实现原理:既然共用资源这么困难那么就不共用,各线程不再共享同一份全局变量,而是各自复制一份,使用数据时各个线程各自取自己的副本,互不干扰。

上一篇 下一篇

猜你喜欢

热点阅读