zoukankan      html  css  js  c++  java
  • PHP pcntl多进程实例

    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);
        }
    }

    使用实例:

    PHP使用PCNTL系列的函数也能做到多进程处理一个事务。比如我需要从数据库中获取80w条的数据,再做一系列后续的处理,这个时候,用单进程?你可以等到明年今天了。所以应该使用pcntl函数了。

    假设我想要启动20个进程,将1-80w的数据分成20份来做,主进程等待所有子进程都结束了才退出:

    $max = 800000;
    $workers = 20;
    $pids = array();
    for($i = 0; $i < $workers; $i++){
      $pids[$i] = pcntl_fork();
      switch ($pids[$i]) {
        case -1:
          echo "fork error : {$i} 
    ";
          exit;
        case 0:
          $param = array(
            'lastid' => $max / $workers * $i,
            'maxid' => $max / $workers * ($i+1),
          );
          $this->executeWorker($input, $output, $param);
          exit;
        default:
          break;
      }
    }
    //回收子进程
    foreach ($pids as $i => $pid) {
      if($pid) {
        pcntl_waitpid($pid, $status);
      }
    }

    这里当pcntl_fork出来以后,会返回一个pid值,这个pid在子进程中看是0,在父进程中看是子进程的pid(>0),如果pid为-1说明fork出错了。

    使用一个$pids数组就可以让主进程等候所有进程完结之后再结束了

  • 相关阅读:
    合并区间
    判断字符串是否是IP
    Python -- 异常处理
    python -- 双下方法
    python -- 判断函数和方法
    python -- 面向对象:反射
    Python -- 面向对象:类的成员
    Python -- 面向对象:类的约束
    Python -- 面向对象的三大特性及深入了解super()
    Python -- mro算法
  • 原文地址:https://www.cnblogs.com/-mrl/p/13132514.html
Copyright © 2011-2022 走看看