并发又被称为共行,是指同时段内处理多个任务。现代计算机系统能够在同一时段内以进程的形式,将多个程序加载到存储器中,由于CPU的时分复用(时间片轮转的抢占式调度方式),能让人产生多个任务在同一个CPU上同时执行的错觉。
注意并发是指逻辑上同时发生,而并行则是指物理上同时发生。
PHP并发模型可以分为多进程模式和多线程模式,具体使用的是哪一种,得看PHP使用的是哪个SAPI(服务器应用程序编程接口)。例如Apache中可能采用多进程模型,也可能采用多线程模型。
Nginx是非阻塞IO&IO复用模型,通过操作系统提供的类似epoll(Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量连接活跃时的系统CPU利用率)功能,可以在一个线程里处理多个客户端的请求。Ngnix每个进程里只有一个线程,但这一个线程可以服务多个客户端。
PHP-FPM是阻塞的单线程模型,PHP-FPM每个进程里只有一个线程,一个进程同时只能服务一个客户端。
注意很多Linux程序都倾向于使用进程而不是线程,因为Linux下相对来说创建进程的开销比较小,而Linux的线程功能又不是很强大。
在实际编程中,实现并发多任务的方式主要有三种:
真正的并行多任务只能在多核CPU上实现。由于任务数量会远远超出CPU的核数,所以操作系统会自动把多个任务轮流调度到每个核心上完成。
大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行,任务执行的一小段时间叫做时间片。由于CPU的执行效率高,时间片非常短,在各个任务之间快速地切换,保障了一个大的时间段里多个任务都能运行(并发)。
安装运行
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,避免子进程进入循环。
相关概念
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是一个高性能的网络框架,使用事件驱动的、异步的、非阻塞的I/O(输入/输出)模型。它可用于开发高性能、可拓展、并发TCP、UDP、Unix套接字、HTTP、Websocket服务。
Swoole在Windows平台上不可用。
Swoole专为构建大规模并发系统而设计,它是用C/C++编写的,并作为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网站采用的分布式系统架构组成成分可能有: