php怎么进行异步编程,5分钟了解PHP异步编程

5be91b11624a3926fe029340da421e29.png

PHP虽然是世界上最好的语言,但总被吐槽不支持异步,经常被隔壁node、golang、java、python四大金刚嘲笑。

f52bdd3e2cfd5246cb8cc4a1d4f09138.gif

其实我们要实现异步也非常简单,之前看到鸟哥的一篇写PHP异步执行的博文 PHP实现异步调用方法研究,这篇文章写于08年距离今天快11年了,这11年里PHP蓬勃发展,对于异步调用也有了更多新的玩法。

一. 先说说鸟哥文章中的几种玩法:

通过渲染前端页面,在浏览器中使用js执行Ajax实现异步请求。这种方式受限于业务场景,只能在浏览器中调用,遇到接口请求就不行了。

通过popen()方法打开一个指向进程的管道,很多人会用这种方式调用bash脚本。这种方式传输数据特别不方便,使用场景有限。

使用CURL扩展,通过设置timeout,将超时时间设置为1秒。不过这种方法会主动断开连接,被调用的服务如果有做连接检测,也会中断服务的执行。比如我们请求 微信的某个费时接口(20s),我们调用1s就断开连接,微信端是否会维持请求执行20S是不可控的。

四方法与CURL类似,通过fsockopen创建socket连接访问远程服务,不循环获取请求结果,可突破CURL扩展timeout最少为1秒的限制。同样会出现连接被断开的问题。

二. 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的支持,能实现调度器的功能,写单进程服务时能大展拳脚。可实现协程,Workerman框架就属于这一类。不过在多进程的服务上没有太大的使用场景,php-fpm就是多进程服务。

还有很多新的特性,等着大家去探索。

94e7a0d052142fe6af455f2738153245.png

三. 最好的异步实现方法

大家都知道PHP是支持多进程的,完全可以新建一个进程去实现异步调用。在论坛里很多人提到PHP的多进程就开启嘲讽模式,嘲讽没关系,但又嘲讽不出什么原因,我很是不解。最后发现大部分人就是跟风。

在PHP中常用两种方式新建进程,一是调用popen()通过管道实现,管道的方式传参异常麻烦,二是通过PHP官方提供的 pcntl 扩展实现。这里推荐使用 pcntl 扩展(默认会安装),如果找不到可到 php-src下载,选择好版本通过phpize安装。最终实现代码如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

/**

* User: layne.xfl

* Date: 2019/02/10

* Time: 下午01:24

*/

class Arrow{

static $instance;

/**

* @return static

*/

public static function getInstance(){

if (null == Arrow::$instance)

Arrow::$instance = new Arrow();

return Arrow::$instance;

}

public function run($rb){

$pid = pcntl_fork();

if($pid > 0){

pcntl_wait($status);

}elseif($pid == 0){

$cid = pcntl_fork();

if($cid > 0){

exit();

}elseif($cid == 0){

$rb();

}else{

exit();

}

}else

{

exit();

}

}

}

//离弦之箭---调用方法

$time_out = 30;

Arrow::getInstance()->run(function() use ($time_out){

//这里写我们要执行的代码

sleep($time_out);

});

我给这个功能取了一个很生动的名字–离弦之箭,表示能同时发射无数弓箭,在弓箭射出去后并不关心它是否射中。代码的主要功能是实现异步调用,比如记录日志,发送10万条短信等。

代码说明:首先Arrow类是个单例类,减少多次调用的开销。run()方法传递一个匿名函数,方便传递各类复杂参数和执行逻辑代码。

『离弦之箭』最难的地方在于多进程的处理。因为要尽可能快的将数据返回给用户,所以主进程越快结束越好。同时又需要子进程来执行耗时的操作,执行完后自行退出。如果不等子进程执行完就将父进程退出会出现什么结果呢?结果是子进程常驻内存变成僵尸进程。有什么办法让子进程执行完之后就自动结束?答案是很难做到……

这里正好可以使用linux进程管理中的一些技巧。如果儿子进程这么不听话,孙子进程会不会听话一点??答案是孙子进程执行结束后会被系统进程回收并销毁(还是孙子听话)。流程如下:当前请求进程fork出子进程,子进程fork出孙子进程,主进程和子进程都先行退出,最后由孙子进程来执行耗时操作,孙子进程执行结束后被系统进程销毁,完美的解决了僵尸进程问题。

这个方法很实用,唯一的缺点是异步调用会多产生一个php-fpm的进程,进程消耗的资源比较多,虽然32位的linux最大进程数是32768,64位的是420万个,但内存和CPU资源没那么多。对于大部分项目这种方法够用了,如果对性能要求比较高,可以继续优化。相比进程,线程消耗的资源较少,协程消耗的资源更少,不过都是后话了。

坚持每周最少三篇原创文章,欢迎分享与留言

13e3d947a73c8b23fdf863adb588898a.png

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值