对PHP并发、多进程、多线程、异步处理、分布式系统的一些了解
作者:互联网
并发
基本概念
并发又被称为共行,是指同时段内处理多个任务。现代计算机系统能够在同一时段内以进程的形式,将多个程序加载到存储器中,由于CPU的时分复用(时间片轮转的抢占式调度方式),能让人产生多个任务在同一个CPU上同时执行的错觉。
注意并发是指逻辑上同时发生,而并行则是指物理上同时发生。
PHP并发模型
PHP并发模型可以分为多进程模式和多线程模式,具体使用的是哪一种,得看PHP使用的是哪个SAPI(服务器应用程序编程接口)。例如Apache中可能采用多进程模型,也可能采用多线程模型。
Nginx是非阻塞IO&IO复用模型,通过操作系统提供的类似epoll(Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量连接活跃时的系统CPU利用率)功能,可以在一个线程里处理多个客户端的请求。Ngnix每个进程里只有一个线程,但这一个线程可以服务多个客户端。
PHP-FPM是阻塞的单线程模型,PHP-FPM每个进程里只有一个线程,一个进程同时只能服务一个客户端。
注意很多Linux程序都倾向于使用进程而不是线程,因为Linux下相对来说创建进程的开销比较小,而Linux的线程功能又不是很强大。
多进程
基本概念
进程
- 进程(process)可理解为是程序运行时的一个实例。进程有两个特性,一是资源的所有权,二是调度执行(指令集)。
- 从内核的角度来看,进程是分配系统资源的基本单位。
- 一个进程至少包含一个线程(主线程)。如何调度进程和线程,由操作系统决定。
多进程
- 子进程结束后,系统内核会负责回收资源。
- 使用多进程比使用多线程更加稳定,因为子进程的异常退出不会导致整个进程退出,父进程还有机会重建流程。
- 一个常驻主进程,只负责任务分发,这样可让逻辑显得很清晰。
- 进程间通信IPC也可以实现数据共享,同样需要加锁,会有同步、死锁问题。
- 使用多进程消息队列,可以采用子进程抢夺队列模式,性能很好。
并发多任务
在实际编程中,实现并发多任务的方式主要有三种:
- . 多进程模式。
- . 多线程模式。
- . 多进程+多线程 模式。
真正的并行多任务只能在多核CPU上实现。由于任务数量会远远超出CPU的核数,所以操作系统会自动把多个任务轮流调度到每个核心上完成。
任务调度
大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行,任务执行的一小段时间叫做时间片。由于CPU的执行效率高,时间片非常短,在各个任务之间快速地切换,保障了一个大的时间段里多个任务都能运行(并发)。
PHP实现多进程
PCNTL
安装运行
php中通过添加PCNTL拓展方式实现多进程需要安装两个模块:pcntl和posix。
在php中进程控制支持(pcntl)默认是关闭的,所以需要使用--enable-pcntl配置选项重新编译php的CGI或者CLI版本以打开进程控制支持。而posix拓展是默认启用的。
centos 操作系统下 运行 yum -y install php-process 即可安装pcntl和posix拓展。
检测是否安装成功的方法:
php -m 查看php在cli模式下已安装的拓展列表。
也可以通过 php -m|grep pcntl 和 php -m|grep posix 来查看。
注意pcntl的多进程实现只能在cli(Command Line Interface,命令行接口)模式下,在Web服务器环境下,会出现难以预期的结果。
重要函数
pcntl_fork():int
在当前进程当前位置产生分支(子进程),父进程和子进程都是从fork(分岔口)的位置继续往下执行,不同的是父进程执行过程中,得到的pcntl_fork函数返回值为子进程号(>0),而子进程得到的pcntl_fork函数返回值为0。pcntl_fork()创建子进程失败时返回-1,不会创建一个子进程,还会引发一个php错误。
pcntl_signal(int $signo,callback $handler,bool $restart_syscalls=true):bool
注册一个信号处理回调函数,可以捕获子进程结束时发出的信号。
pcntl_wait(int &$status,int $options=0,array &$rusage=?):int
挂起当前进程的执行直到子进程退出,或者直到传递了一个信号,其动作是终止当前进程或调用信号处理函数。如果调用此函数时子进程已经退出(俗称“僵尸”进程),则此函数立刻返回,释放子进程使用的任何系统资源。
pcntl_waitpid(int $pid,int &$status,int $options=0):int
$pid值为-1时等同于pcntl_wait函数。pcntl_wait和pcntl_waitpid函数的一大作用就是避免出现父进程还没回收完子进程就早早退出的情况。
注意事项
如果是在循环中创建了子进程,那么子进程最后要exit,避免子进程进入循环。
PHP-FPM
相关概念
cgi:通用网关接口。
php-cgi:php解释器。
Fastcgi:用来提高cgi程序(如php-cgi)性能的方案/协议。
php-fpm可以理解为是实现了Fastcgi的程序,官方对于fpm的解释是Fastcgi Process Manager(Fastcgi进程管理器)。
简单了解
php-fpm提供了更好的php进程管理方式,可以有效控制内存和进程,可以平滑重载php配置。
php-fpm可以通过php-fpm.conf配置文件做具体配置。如pm.max_children指定的是最大的进程数量,pm.max_requests指定的是每个进程处理完多少个请求后重启(避免内存泄漏)。
php-fpm的多进程模式可以分为静态模式和动态模式。静态模式是指直接开启指定数量的php-fpm进程,不再增加或者减少。动态模式是指开始时启动一定数量的php-fpm进程,然后根据实际情况的变化动态地增加进程或者释放进程。
php-fpm作为php的多进程管理器,当用Nginx作为Web Server时,来自用户的请求会根据Nginx的路由配置把以php为后缀名的文件转发给php-fpm。
Swoole
Swoole是一个高性能的网络框架,使用事件驱动的、异步的、非阻塞的I/O(输入/输出)模型。它可用于开发高性能、可拓展、并发TCP、UDP、Unix套接字、HTTP、Websocket服务。
Swoole在Windows平台上不可用。
Swoole专为构建大规模并发系统而设计,它是用C/C++编写的,并作为PHP的拓展安装。
多线程
基本概念
线程
- 线程(thread)是进程的实际运作单位,是CPU调度和分配的基本单位。
- 一个线程是指进程中一个单一顺序的控制流程。
- 一个进程中可以并发多个线程,每个线程可执行不同的任务。
- 同一个进程内的线程可以共享内存变量,实现进程内通信。线程读写变量时存在同步问题,需要加锁。
- 线程比进程更加轻量级,对系统资源的消耗更少。
- 某个线程运行出现严重错误,会导致整个进程崩溃。
多线程
- 多线程的创建和切换的系统开销一般比多进程要小,所以一定程序上会比多进程更高效。
- 一个多线程程序比单线程程序被操作系统调度的概率更大,所以多线程程序一般比单线程程序更高效。
- 多线程程序的多个线程可以在多个cpu的多个核心同时进行,可以充分发挥机器多核的优势。
- 多线程方案也是可以优化的,乱用多线程不一定就能使程序运行得更高效,因为线程的创建和销毁、上下文切换、线程同步等也是有性能损耗的,耗费时间可能比顺序执行的代码还多。
- PHP为多线程的应用模型提供了一个安全机制-Zend(禅)线程安全(Zend Thread Safe,ZTS)。
- 是否使用多线程还要根据具体情况来定,一般考虑以下两种情况:
- .代码中I/O多(I/O阻塞会使操作系统发生任务调度,阻塞当前任务)的情况下,可考虑使用多线程时将代码并行。例如多次读整块的文件,或请求多个网络资源。
- .多线程能充分利用CPU,所以有多处大计算量代码时,可以使用多线程让它们并行执行。
PHP实现多线程
PHP默认不支持多线程,如果需要用到多线程,则需要安装pthread拓展(PHP版本>5.3)。而安装pthread拓展,必须用--enable-maintainer-zts参数重新编译PHP,这个参数是指定编译PHP时使用线程安全方式。
PHP实现的线程安全主要是通过TSRM机制(可理解为是:线程安全运行机制)对全局变量和静态变量进行了隔离,将全局变量和静态变量给每个线程复制了一份,各线程使用的全局变量和静态变量都是主线程使用的全局变量和静态变量的一个备份,从而避免了变量冲突,也就避免了出现线程安全问题。
PHP对多线程的封装保证了线程安全,程序员不需要因使用多线程考虑给全局变量加各种锁以避免读写冲突,同时减少了出错的机会,写出来的代码更加安全,这保持了PHP简单易用的一贯风格。但由此导致了子线程一旦开始运行,主线程便无法对子线程运行细节进行调整,线程一定程度上失去了线程之间通过全局变量进行消息传递的能力。
PHP开启线程安全选项后,使用TSRM机制分配和使用变量时会有额外的损耗,因此在不需要使用多线程的PHP环境中,可以考虑使用PHP的NTS(非线程安全)版本。
虽然每个PHP脚本的执行是单线程的,但是Web服务器组件如Apache可采用多线程模型,可以通过Web服务器本身的多线程来处理客户端请求,实现多线程处理的效果。
异步处理
PHP的执行默认是阻塞式的,当需要执行很耗时的操作,而其结果又不需要返回给客户端时,就可以考虑采用异步处理的方式完成。
可实现异步处理的方式:
1. 推荐使用fsockopen函数,此函数的功能为初始化一个套接字连接到指定主机。默认情况下将以阻塞模式开启套接字连接,可以通过stream_set_blocking()将其切换为非阻塞方式开启套接字连接。注意在此函数实际执行中,客户端连接断开或者连接超时都可能导致执行不完整,因此需要加上:
ignore_user_abort(true); //忽略客户端断开。
set_time_limit(0); //取消脚本最大执行时间限制。
2. 使用curl。设置curl的超时时间CURLOPT_TIMEOUT为1,因此客户端需要等1秒钟。
分布式系统
分布式网络存储技术是将数据分散地存储在多台服务器上。分布式网络存储系统采用可拓展的系统结构,利用多台服务器分担存储负荷,使用位置服务器定位存储信息。
php网站采用的分布式系统架构组成成分可能有:
- .Nginx反向代理。
- .静态资源服务器/CDN(内容分发网络)。
- .Web集群(运行相同的Web应用)。
- .NAS(网络附属存储)文件服务器/OSS(对象存储)。
- .Redis服务器(Web集群内共享Session)。
- .队列系统等(其它语言的后端支持)。
- .分布式Mysql(主数据库、从数据库、备用主数据库)。
标签:PHP,线程,分布式系统,进程,pcntl,多线程,php 来源: https://blog.csdn.net/ylnzzl/article/details/119115762