作为程序员一定要保持良好的睡眠,才能好编程

php多进程多线程多协程处理任务

发布时间:2019-03-27

PHP本身不支持多进程,需要借助 pcntl   (是process control的缩写)


php -m 查看开启的功能模块, 也可以使用 phpinfo(); 这样的函数再浏览器中查看。



多进程是运行再 CLI 模式下的。


创建进程在php端使用 pcntl_frok() 命令进行创建。


pcntl_frok() 返回进程ID  有三种情况:

-1 创建进程失败

0 子进程创建成功

如果返回大于等于1 则父进程会得到子进程号,处理父进程的逻辑


$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
    //错误处理:创建子进程失败时返回-1.
     die('could not fork');
} else if ($pid) {
     //父进程会得到子进程号,所以这里是父进程执行的逻辑
     pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
     //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}



进程与进程之间如何通讯呢?  pipe 管道 有名管道、无名管道 、共享内存 、套接字


如何创建管道呢?



http://www.xiaosongit.com/index/detail/id/540.html




$desc=[


['pipe','r'], //stdin

['pipe','w'], //stdout

['file','/tmp/pipe.log','a'] //stderr


];

proc_open($cmd,$desc,$pipes);

$pipes 这是一个引用变量,第0个元素 用于写   第一个元素用于读取




管道如何操作?

fwrite()


fclose()


fread()


和读取文件差不多,就是使用这些函数进行操作,唯一一点是 创建管道的方式不同、



如果一个任务被分解成多个进程执行,就会减少整体的耗时。




PHP多进程原理和基础代码实现,


<?php
$ppid = posix_getpid();
$pid = pcntl_fork();
if ($pid == -1) {
    throw new Exception('fork子进程失败!');
} elseif ($pid > 0) {
    cli_set_process_title("我是父进程,我的进程id是{$ppid}.");
    sleep(30); // 保持30秒,确保能被ps查到
} else {
    $cpid = posix_getpid();
    cli_set_process_title("我是{$ppid}的子进程,我的进程id是{$cpid}.");
    sleep(30);
}








参考了http://www.php.cn/php-weizijiaocheng-360383.html php 多进程插入数据


首先创建sql表:

CREATE TABLE `pcntl_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rand_id` int(11) DEFAULT NULL,
  `content` varchar(200) DEFAULT NULL,
  `datetime` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=400006 DEFAULT CHARSET=latin1


先记录下来吧

/**
 * 模拟并发请求,10万次写入数据库
 * 拆分为10个进程,每个进程处理一万条插入
 */

$total = 100000;
$num   = 10;
$per   = $total/$num;
echo 'start '.microtime(true).PHP_EOL;
for($i = 1; $i<= $num; $i++)
{
    $pid = pcntl_fork();
    if($pid == -1) {
        die('fork error');
    }
    if($pid > 0) {
        //$id = pcntl_wait($status,WNOHANG);
        $child[] = $pid;
    } else if ($pid == 0) {
        $link  = mysqli_connect('localhost','root','song','test');
        $start = ($i-1)*$per + 1;
        $end   = $start + $per;
        $autoStr="qwertyuiosdfghjklxcvbnm,rtyuisdfghj34567890ghjkl;kjhjgh";
        for($j = $start; $j< $end; $j++){
            $time = microtime(true);
            $content=substr(str_shuffle($autoStr),0,rand(10,strlen($autoStr)));
            $sql = 'insert into pcntl_test (rand_id,content,datetime) values ('.$j.',"'.$content.'","'.$time.'")';
             mysqli_query($link,$sql);
        }   
        mysqli_close($link);
        $id = getmypid();
        echo 'child '.$id.' finished '.microtime(true).PHP_EOL;
        exit(0);
    }   
}
  
while(count($child)){
    foreach($child as $k => $pid) {
        $res = pcntl_waitpid($pid, $status, WNOHANG);
        if ( -1 == $res || $res > 0) {
            unset($child[$k]);
        }
    }
}
echo 'end '.microtime(true).PHP_EOL;


当$total=100000,$num = 10;执行结果如下:


start 1553663524.3773
child 6009 finished 1553663565.4548
child 6008 finished 1553663565.6173
child 6011 finished 1553663565.6507
child 6013 finished 1553663565.7661
child 6014 finished 1553663565.8289
child 6010 finished 1553663565.906
child 6015 finished 1553663565.9836
child 6016 finished 1553663566.0047
child 6017 finished 1553663566.0258
child 6012 finished 1553663566.1954
end 1553663566.2186
总时间为41.8413秒


当$total=100000,$num = 100时,执行结果如下:


start 1491904334.1735
child 20085 finished 1491904337.0712
child 20086 finished 1491904337.144
……
child 20262 finished 1491904341.5602
child 20264 finished 1491904341.5803
end 1491904341.5869
总时间为7.413399934768677


当$total=10000,$num = 1000时,执行结果如下:

start 1491904562.0166
child 20282 finished 1491904562.1191
child 20277 finished 1491904562.1268
child 20279 finished 1491904562.1352
...
child 21586 finished 1491904576.6954
child 21582 finished 1491904576.7024
child 21584 finished 1491904576.7226
end 1491904576.7297

总时间为14.71310019493103,相比100个子进程,耗时更长了。


进程切换太多,影响了了效率应该是原因之一


当$total=100000 ,$num=100时,十万条记录,100个进程插入

start 1491905670.2652
child 21647 finished 1491905725.4382
child 21651 finished 1491905725.4595
child 21642 finished 1491905725.5402
....
child 21810 finished 1491905729.7709
child 21812 finished 1491905729.8498
child 21811 finished 1491905729.9612
end 1491905729.9679

总时间为59.70270013809204


单进程插入1万条数据,耗时18秒,相对10个进程插入1万记录来说,耗时少些。

而单进程插入10万条记录,耗时187.40066790581,相对来说,是挺慢的了。三分钟。。。


进程启动的数量数,并不是越多就越好,而是根据自己的服务器情况,以及处理的数据来判断,由于多个进程间相互切换也是需要资源的。



不过,本人再fork 1000个进程,来插入10万记录时,成功的情况下36秒左右,也可能会出现错误,mysqli_connection返回false,是不是连接数受限制了?


fork 一万个子进程,插入一百万数据,这时,出现连接错的情况就很多了。最后耗时360秒,数据表中插入了945300条记录,成功率94.53%。于是查看数据库的相关配置信息


mysql>  show global status like '%connect%';
+-----------------------------------------------+---------------------+
| Variable_name                                 | Value               |
+-----------------------------------------------+---------------------+
| Aborted_connects                              | 0                   |
| Connection_errors_accept                      | 0                   |
| Connection_errors_internal                    | 0                   |
| Connection_errors_max_connections             | 628                 |
| Connection_errors_peer_address                | 0                   |
| Connection_errors_select                      | 0                   |
| Connection_errors_tcpwrap                     | 0                   |
| Connections                                   | 16519               |
| Locked_connects                               | 0                   |
| Max_used_connections                          | 501                 |
| Max_used_connections_time                     | 2017-04-12 15:19:54 |
| Performance_schema_session_connect_attrs_lost | 0                   |
| Ssl_client_connects                           | 0                   |
| Ssl_connect_renegotiates                      | 0                   |
| Ssl_finished_connects                         | 0                   |
| Threads_connected                             | 4                   |
+-----------------------------------------------+---------------------+

 


 


mysql>  show global variables like '%connect%';
+-----------------------------------------------+--------------------+
| Variable_name                                 | Value              |
+-----------------------------------------------+--------------------+
| character_set_connection                      | utf8mb4            |
| collation_connection                          | utf8mb4_general_ci |
| connect_timeout                               | 10                 |
| disconnect_on_expired_password                | ON                 |
| init_connect                                  |                    |
| max_connect_errors                            | 100                |
| max_connections                               | 500                |
| max_user_connections                          | 0                  |
| performance_schema_session_connect_attrs_size | 512                |
+-----------------------------------------------+--------------------+


修改 myqsql 配置文件,/etc/my.cnf


把max_connections 改为10000,然后重启mysql


实际MySQL服务器允许的最大连接数16384;


结果然并卵,虚拟机好像挂了了。


并发量大的时候,问题就出在了连接mysql这里。

可以通过一个连接池来尝试解决该问题。