Blogger Information
Blog 2
fans 0
comment 0
visits 863
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
PHP 异步
手机用户023513366
Original
521 people have browsed it

  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 出孙子进程,主进程和子进程都先行退出,最后由孙子进程来执行耗时操作,最后完美的解决了僵死进程问题。

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post