pcntl
是一个可以利用操作系统的fork
系统调用在PHP中实现多线程的进程控制扩展,当使用fork
系统调用后执行的代码将会是并行的。pcntl
仅适用于Linux平台的CLI模式下使用。
PHP官方没有提供多线程的扩展,在pecl
中有一个pthread
扩展提供了多线程的特性,此版本仅在线程安全版本中可用。
创建子进程pcntl_fork
int pcntl_fork(void)
当pcntl_fork
函数执行时会在当前进程下创建一个子进程,子进程与父进程在PID和PPID上会不同。
子进程会复制父进程中所有的数据、代码、状态等信息。当使用pcntl_fork
成功创建子进程后,子进程会复制父进程的代码和数据。此时父进程和子进程拥有相同的代码和数据。子进程也会复制父进程的状态。
当使用pcntl_fork
创建子进程,如果成功则会在父进程中将会返回0,在子进程中会返回自身的进程编号PID
。如果创建失败则返回-1
。
使用pcntl_fork
创建的进程只是一个分支节点,相当于一个标记,父进程完成后子进程会从标记处继续执行,也就是说在pcntl_fork
之后的代码分别会被父进程和子进程执行两遍,而两个进程在执行过程中得到的返回值却是不同的,因此才可以分离父子进程执行不同的代码。
在Linux环境下可使用ps
命令查看进程
<?php $pid = pcntl_fork();if($pid > 0){ //父进程 exit(0); }elseif($pid == 0){ //子进程 exit(0); }
多进程与多线程的区别
多进程和多线程的作用相同,区别主要在于
多个线程是在同一个进程内的,线程之间可以共享内存变量而实现线程间的通信。
线程比进程更加轻量级,进程要比线程更加消耗系统资源。
多线程存在的问题主要有
线程读写变量存在着同步问题需要加锁
锁粒度过大会存在性能问题,会导致只有一个线程在运行,其它线程都在等待锁,也就无法实现并行。
同时使用多个锁时逻辑复杂,一旦某个锁没有被正确释放可能会发生线程死锁。
某个线程发生致命错误会导致整个进程崩溃
相对而言多进程更为稳定,可利用进程间通信IPC
技术实现数据共享。多进程通信的方式主要包括
共享内存
共享内存和线程间读写变量时一样的,都需要加锁,同时也存在同步、死锁等问题。消息队列
消息队列采用多个子进程抢占队列的模式,性能较好。管道、UnixSock、TCP、UDP
可以使用read/write
来传递数据,TCP/UDP使用socket来通信,子进程可以分布运行。
利用fork
系统调用可以实现并发的TCP服务器,主进程accept
客户端连接。当有新的连接到来时直接fork
一个子进程,子进程中循环recv/send
处理数据。这种模式在请求量不多的情况下很实用,例如FTP服务器。
在过去多数Linux程序都时采用这种模式,简单高效,代码量少。当有几百个并发的情况下表现不错,但在大并发的情况下消耗就会过大。
例如:每个子进程都能创建一个与之对应的文件,父进程也创建一个属于自己的文件。
<?php $socket = socket_create(AF_INET, SOCK_STREAM, 0); if($socket < 0){ $errmsg = socket_strerror($socket); echo "failed to create socket: {$errmsg}".PHP_EOL; exit;}$host = "0.0.0.0";$port = 9601;$ret = socket_bind($socket, $host, $port);if($ret < 0){ echo "failed to bind socket: {$ret}".PHP_EOL; exit;}$ret = socket_listen($socket, 0);if($ret < 0){ $errmsg = socket_strerror($ret); echo "failed to listen: {$errmsg}".PHP_EOL; exit;}while(pcntl_fork() == 0){ $connection = @socket_accept($socket); if(pcntl_fork() == 0){ $recv = socket_read($connection ,8192); $data = "serverr: {$recv}"; socket_write($connection ,$data); socket_close($connection); exit(0); }else{ socket_close($connection); } }