php如何异步地执行代码?

比如要记录些日志数据,我并不需要实时入库,我想代码异步执行后立马执行下面的代码,我也并不需要回调处理,如果把数据发给消息队列,这也需要时间吧,我想在毫秒级就继续下面的代码了,至于他把数据传给谁,入库花了多少时间我并不关心,举个例子,
echo 1;
log(数据);
echo 2;
我想log函数后台执行,至于花多少时间,我不管,但是我不用去等待他,对于我来说就是毫秒级执行完了似的。

阅读 10.4k
6 个回答
//程序被阻塞10秒
shell_exec('timeout 10 vmstat 1 >/dev/null 2>&1 &');

//程序不会被阻塞
pclose(popen('timeout 10 vmstat 1 >/dev/null 2>&1 &', 'r'));

//因此可以异步执行任务
pclose(popen("timeout 60 php /path/to/task.php '$arg' >/dev/null 2>&1 &", 'r'));

其中变量$arg是传递给脚本task.php的参数,task.php里通过$argv[1]拿到这个参数.
timeout 60 表示task.php脚本的最大执行时间60秒,不需要的话可以去掉.
pclose(popen())实现异步的本质是打开一个进程去执行阻塞代码,
适用于不要求执行完成后自动返回结果(回调)的异步场景.

字符串参数$arg可以用单引号括起来,可以避免一些空格的影响,但还是有缺陷.
字符串参数最好还是serialize序列化到文件,
然后给脚本task.php传文件路径这个参数,
让task.php自己读文件unserialize反序列化拿数据.
文件名应该做到唯一,比如可以是用户ID或者进程PID加上时间随机数:

$filename = md5(uniqid($uid.'_',        true));
$filename = md5(uniqid($getmypid().'_', true));

发给消息队列 VS 毫秒级别 >>> 矛盾点在哪里?

用 Redis 可以做队列吧,Redis 操作每秒读写上万次 ,你把消息放到 Redis 里面也就 1次写操作吧,时间消耗低于毫秒吧。
所以我觉得 使用队列,完全可以达到你说的毫秒级的需求吧。

另外,不管通过何种手段执行异步操作,总要伴随时间开销,不可避免。

你的这个需求不需要用到异步,或者说有更好的解决方案是用UDP协议发送日志,UDP协议是无连接不阻塞的,几乎没有时间损耗,比较适合大量日志发送的场景。
给你一个基于 Workerman 的日志系统做参考:https://github.com/tmtbe/Logg...

根据你的描述我觉得队列可以实现这个需求。

"我想log函数后台执行,至于花多少时间,我不管,但是我不用去等待他,对于我来说就是毫秒级执行完了似的。"

log 函数可以修改为类似这样 dispatch(new Log(数据)),dispatch 函数负责把指定任务推送到任务队列,并不是立即执行。
至于你说的 异步php, 很遗憾的告诉你,php 在脚本模式下是不存在异步的。不过你可以看看 swoolereactphpworkerman 之类的 php库,然而这种运行模式已经不是脚本的运行方式了,因为常驻内存了,也没有用 php-fpm 来管理 cgi进程

可能没描述好。。。

新手上路,请多包涵

php还得用队列,确实麻烦。
go 直接调用协程就行

go storeLog("....")

原生php就可以实现你要的功能,不需要wokerman也不需要swoole。用fsockopen模拟http请求,不fread就可以实现你要求的功能不管数据返回。
PHP7的话 curl也可以支持毫秒级别的请求;

/**
* 异步请求
* 
* @author 柳下惠下柳(presleylee)
* @param string $str_url
* @param array $arr_postData
* @return bool
*/
function asyncRequest($str_url, $arr_postData = []) {
   $str_method = 'GET';
   if ($arr_postData) {
      $str_method = 'POST';
   }
   $arr_url = parse_url($str_url);
   $int_port = isset($arr_url['port']) ? : 80;
   $res_fp = fsockopen($arr_url['host'], $int_port, $int_error, $str_errstr, 30);
   if (!$res_fp) {
      return false;
   }
   $str_requestPath = $arr_url['path'] . "?" . $arr_url['query'];
   $str_header = $str_method . " " . $str_requestPath;
   $str_header .= " HTTP/1.1\r\n";
   $str_header .= "Host: " . $url_array['host'] . "\r\n";
   $str_header .= "Connection:Close\r\n\r\n";
   
   if (!$arr_postData) {
      $str_tmpPost = strval(NULL);
      foreach ($arr_postData as $k => $v) {
          $str_tmpPost .= $k . "=" . $v . "&";
      }
      $str_postString .= "Content-Type:application/x-www-form-urlencoded\r\n";
      $str_postString .= "Content-Length:". strlen($str_tmpPost) . "\r\n;";
      $str_header .= str_tmpPost . "\r\n\r\n";
   }
   
   fwrite($res_fp, $str_header);
   fclose($res_fp);
   return true;
}

$str_url = 'https//api.aaa.com/async/log';
$arr_postData = [
   'field1' => 'aaaa'
];

asyncRequest($str_url, $arr_postData);

接收请求:

<?php
ignore_user_abort(TRUE);
set_time_limit(0);

//要执行的代码
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏