博客列表 >PHP 异步

PHP 异步

手机用户023513366
手机用户023513366原创
2022年11月11日 11:15:24519浏览

  PHP 是世界上最好的语言,但是总被 “同行们” 吐槽不支持异步。其实我们要实现异步也非常简单,之前看到一篇写 PHP 异步执行的博文 PHP 实现异步调用方法研究,这篇文章还是 08 年的,到今天 PHP 发展快 10 年了,对于异步调用也有了更多新的玩法。
  
  一、先说说文章中已有的几种玩法:
  
  一是通过渲染前端页面,使用 js 执行 Ajax,这种方式现在还适用。只是受限于业务场景,因为只能在浏览器中调用,遇到接口请求就不行了。
  
  二是通过 popen () 方法打开一个指向进程的管道,每个请求会多起一个进程。忽略进程来看最主要的原因是数据的传输特别不方便,使用场景有限。
  
  三是使用 CURL 扩展,通过设置 timeout 超时参数,能实现离弦之箭的效果。不过这种方法会主动断开连接。被调用的服务如果有做连接检测,也会中断服务端脚本的执行。比如我们请求 微信的某个费时接口(20s),我们调用 1s 就断开连接,微信端是否会维持请求执行 20S 是不可控的。所以这种方法不推荐大家使用。
  
  四方法与 CURL 类似,通过 fsockopen 创建 socket 连接访问远程服务,不循环获取请求结果。一样会有三中连接被断开的问题。
  
  二、 PHP 发展了这么多年对异步支持方面都有哪些改进?
  
  CURL 扩展已支持毫秒配置,将 CURLOPT_TIMEOUT 改为 CURLOPT_TIMEOUT_MS 即可生效(cURL 版本 >= libcurl/7.21.0,老服务器要检查版本),但还是我前面说的需要服务端配合,不然接口的调用结果不可控。
  
  CURL 扩展已支持并发,我们能一次访问 N 个接口,执行时间取最长接口的时间。比如我们能一次访问 京东支付(1s),微信支付 (1.2s),支付宝 (0.8s) 不同服务的三个接口,总耗时才 1.2s。详细用法 curl_multi_init
  
  类似 Node.js 的异步 IO 框架 Swoole,能很好的实现异步调用;不过 Swoole 理论上不能算 PHP 框架,他算是 PHP 功能的扩展。所以除非项目都用 Swoole 写,不然也是享受不到异步 IO 的福利。
  
  对 yield 的支持,能实现调度器的功能,写单进程的服务时能大展拳脚,特别是实现协程,异步更不在话下。不过在多进程的 web 服务上没有太大的使用场景,看未来会不会有新的玩法吧。
  
  三、最好的异步实现方法
  
  我们都知道 PHP 是支持多进程编程的,那完全可以新建一个进程去实现异步的调用。比如调用 popen () 方法,但是管道的方式传参异常麻烦,不过多进程这个方法是绝对可行的。如果要实现多进程的功能,毫无疑问我们会选择 PHP 官方提供的 pcntl 扩展,PHP 默认会安装 pcntl 扩展,如果代码运行提示找不到 pcntl 扩展,可自行到 php-src 下载,选择好版本通过 phpize 安装即可。代码如下

  1. <?php
  2. /**
  3. * User: layne.xfl
  4. * Date: 2017/5/12
  5. * Time: 下午01:24
  6. */
  7. class Arrow{
  8. static $instance;
  9. /**
  10. * @return static
  11. */
  12. public static function getInstance(){
  13. if (null == Arrow::$instance)
  14. Arrow::$instance = new Arrow();
  15. return Arrow::$instance;
  16. }
  17. public function run($rb){
  18. $pid = pcntl_fork();
  19. if($pid > 0){
  20. pcntl_wait($status);
  21. }elseif($pid == 0){
  22. $cid = pcntl_fork();
  23. if($cid > 0){
  24. //这里放空
  25. }elseif($cid == 0){
  26. $rb();
  27. }else{
  28. exit();
  29. }
  30. }else
  31. {
  32. exit();
  33. }
  34. }
  35. }
  36. //离弦之箭---调用方法
  37. $time_out = 30;
  38. Arrow::getInstance()->run(function() use ($time_out){
  39. //这里写我们要执行的代码
  40. sleep($time_out);
  41. });

  
  我给这个功能取了一个很生动的名字 — 离弦之箭。代表异步调用,我们的弓箭射出去后并不关心它的结果因为发送这个动作做了就行。比如打个 10M 的 log,通知 10 个人(发 10 条短信)。
  
  代码说明:首先 Arrow 类是个单例类,减少多次调用的开销。run () 方法传递的是一个匿名函数,这样我们能非常方便的传递参数,并且保留上下文。
  
  这个类最难的地方在于多进程的处理。因为我们要尽可能快的将数据返回给用户,所以主进程越快结束越好。但是我们又需要子进程来执行我们耗时的操作,执行完退出才行。如果不等子进程执行完就将父进程退出会出现什么结果呢?结果就是子进程会常驻内存变成僵死进程。那我们有什么办法让子进程执行完之后就自动结束呢?答案是很难…… 那么儿子进程这么不听话,孙子进程会不会听话一点呢??答案是孙子进程执行结束后会被系统进程回收并销毁(还是孙子听话)。所以我在代码中使用了如下方法:当前请求进程 fork 出子进程,子进程 fork 出孙子进程,主进程和子进程都先行退出,最后由孙子进程来执行耗时操作,最后完美的解决了僵死进程问题。

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议