php启用 asynchdns,在 PHP 中使用 Promise + co/yield 协程

摘要: 我们知道 JavaScript 自从有了 Generator 之后,就有了各种基于 Generator 封装的协程。其中 hprose 中封装的 Promise 和协程库实现了跟 ES2016 的 async/await 一样的功能,并且更加灵活。我们还知道 PHP 自从 5.5 之后,也引入了 Generator,同样也有了各种基于它封装的 PHP 协程库,hprose 同样也为 PHP 提供的跟 JavaScript 版本类似的 Promise 和协程库。下面我们就来看一下它跟 swoole 结合的效果。

为什么需要异步方式

一个函数执行之后,在它后面顺序编写的代码中,如果能够直接使用它的返回结果或者它修改之后的引用参数,那么我们通常认为该函数是同步的。

而如果一个函数的执行结果或者其修改的引用参数,需要通过设置回调函数或者回调事件的方式来获取,而在其后顺序编写的代码中无法直接获取的话,那么我们通常认为这样的函数是异步的。

PHP 提供的大部分函数都是同步的。通常我们会有一个误解,那就是容易把同步和阻塞当成同一个概念,但实际上同步代码不一定都是阻塞的,只是同步代码对阻塞天然友好,当同步代码和阻塞结合时,代码通常是简单易懂的。

阻塞带来的问题是当前线程(或进程)会陷入等待,一直等到阻塞结束,这样就会造成线程(或进程)资源的浪费。所以,通常认为阻塞是不够高效的。

但是如果要编写非阻塞代码,使用同步方式会变得有些复杂,且不够灵活。同步方式的非阻塞代码通常会使用 select 模式,例如 curl_multi_select, stream_select, socket_select 等就是 PHP 中提供的一些典型的 select 模式的函数。

我们说它复杂且不够灵活是有理由的,例如使用上面的 select 模式编写同步的非阻塞代码时,我们需要先构造一个并发任务的列表,之后手动构造循环来执行这些并发的任务,在循环开始之后,虽然这几个任务可以并发,但是这个循环相对于其后的代码总体上仍然是阻塞的,我们要想拿到这些并发任务的结果时,仍然需要等待。select 虽然可以同时等待多个任务中某一个或几个就位后,再执行后续操作,但仍然有一部分时间是被等待消耗掉的。而且如果是纯同步非阻塞的情况下,我们也很难在循环开始后,动态添加更多的任务到这个循环中去。

所以,如果我们希望程序能够更加高效,更加灵活,就需要引入异步方式。

传统的异步方式有什么问题

一提到异步模式,大家脑子中的第一印象可能就是回调、回调、回调。是的,这是最简单最直接也是之前最常见的异步模式。只要在调用异步函数时设置一个或多个回调函数,函数就会在完成时自动调用回调函数。或者为一个对象设置一堆事件,之后调用该对象上的某个异步方法,虽然这个异步方法本身可能不再需要设置回调函数,但是设置的这堆事件实际上跟回调函数所起到的作用是一样的。

如果你的程序逻辑够简单,简单的一两层回调也许并不会让你觉得异步方式的编程有什么麻烦。但如果你的程序逻辑一旦有些复杂,你可能就会被层层回调搞得疲惫不堪了。当然,实际上你的程序需要层层回调的原因,也许并不是你的程序逻辑真的复杂,而是你没有办法将回调函数中的参数结果传出来,所以,你就不得不将另一个回调函数传进去。

我们来举一个简单的例子,假设我们有 1 个同步函数:

function sum($a, $b) {

return $a + $b;

}

然后我们按照下面的方式去调用它:

$a = sum(1, 2);

$b = sum($a, 3);

$c = sum($b, 4);

var_dump(array($a, $b, $c));

虽然上面的代码很不精简,但我们要表达的意图很明确,而且代码看起来很清楚。

那接下来我们把这个函数换成一个形式上的异步函数,例如:

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

当然,它的执行并不是异步的,这里我们先不关心它的实现是不是真异步的。

现在如果要做上面同样的操作,代码就要这样写了:

async_sum(1, 2, function($a) {

async_sum($a, 3, function($b) use ($a) {

async_sum($b, 4, function($c) use ($a, $b) {

var_dump(array($a, $b, $c));

});

});

});

代码的执行结果是一样的。但异步的代码看起来显然更难读一些,虽然这已经是很简单的例子了。

好了,看到这里,有些读者可能会觉的我上面的这个例子很糟糕。因为明明有同步的函数可以使用,并且代码清晰可读,为啥非要写个形似异步的函数,把本来同步可以做的很好的事情用异步方式复杂化呢?而且那个异步调用的方式,最后不还是想要实现同步化的结果吗?

如果你这么想的话,一点都没错。但我们这里想要解决的问题是,如果我们拿到的只有一个异步函数,这个函数没有同步实现,我们也不知道这个异步函数的内部定义是怎样的,我们也没办法将这个异步函数改为同步函数实现。那我们有没有办法将上面的程序改的更可读一些呢?

当然是可以的,所以,现在 Promise 要登场了。

为什么要引入 Promise

通常我们对 Promise 的一个误解就是,它要解决的是层层回调的问题,比如上面的问题看上去就是一个典型的层层回调的问题。

然而实际上,Promise 要解决的并不是回调不回调的问题,如果你使用过 Promise 的话,你会发现使用 Promise 你仍然少不了要使用回调。Promise 要解决的问题是,如何将回调方法的参数从回调方法中传递出来,让它可以像同步函数的返回结果一样,在回调函数以外的控制范围内,可以传递和复用。

下面这几篇文章可能会对大家理解 Promise 有所帮助:

我觉得这几篇文章讲的比较透彻,所以我就不重复文章中的内容了。

下面我们来看上面的例子用 Promise 如何解。

我们现在用最简单粗暴的方式来引入 Hprose 的库,直接复制源码而不是使用 composer。然后我们在代码中直接使用:

require_once("Hprose.php");

use Hprose\Promise;

这种方式来引入 Hprose 的 Promise 库,当然你也可以写成:

require_once("Hprose.php");

use Hprose\Future;

Future 库跟 Promise 库基本上是一样的,你可以认为 Future 是 Promise 的具体实现,Promise 只是 Future 实现的一个包装。这个区别你可以从源码中直接看出来,这里就不多做解释了。

接下来,我们要把前面的 async_sum 函数 Promise 化,Hprose 提供了这样一个函数:Promisepromisify(或者 Futurepromisify),它的作用就是将一个使用回调方式的异步函数变成一个返回 Promise 对象的异步函数。这样说,也许有些不好理解,下面直接上代码:

require_once("Hprose.php");

use Hprose\Promise;

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

$sum = Promise\promisify('async_sum');

$a = $sum(1, 2);

$b = $a->then(function($a) use ($sum) {

return $sum($a, 3);

});

$c = $b->then(function($b) use ($sum) {

return $sum($b, 4);

});

Promise\all(array($a, $b, $c))->then(function($result) {

var_dump($result);

});

好了,看到这里,如果你对 Promise 的理解还不够深入的话,你的第一反应可能是:这不是把程序变得更复杂了吗?原来的程序是 3 个回调,现在仍然是 3 个回调,还多了包装,都玩出花来了,有意思吗?

确实,从上面的代码来看,代码并没有被简化,但是你会发现,现在回调函数中的参数已经通过 Promise 返回值的方式传递出来了,而且可以在原本的回调函数控制范围以外被传递和复用了。

但是你可能会说然并卵,程序不是仍然很复杂吗?那我们就来进一步简化一下:

require_once("Hprose.php");

use Hprose\Promise;

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

$sum = Promise\wrap(Promise\promisify('async_sum'));

$var_dump = Promise\wrap('var_dump');

$a = $sum(1, 2);

$b = $sum($a, 3);

$c = $sum($b, 4);

$var_dump(Promise\all(array($a, $b, $c)));

现在,代码中再也看不到回调了。因为我们把函数包装成了可以接收 Promise 变量的函数。当然,其实现细节略微有些复杂,如果你感兴趣,可以去看一下源码,这里就不做源码剖析了。如果感兴趣的读者多得话,以后有时间再写源码剖析。

当然,如果你只是想把异步调用同步化,除了 Promisewrap 外,你还可以通过 co/yield 协程来实现。

Hprose 中的 co/yield 协程

还是上面的例子,如果你使用的是 PHP 5.5 或者更高版本,那么你可以这样来写代码了。

require_once("Hprose.php");

use Hprose\Promise;

function async_sum($a, $b, $callback) {

$callback($a + $b);

}

Promise\co(function() {

$sum = Promise\promisify('async_sum');

$a = (yield $sum(1, 2));

$b = (yield $sum($a, 3));

$c = (yield $sum($b, 4));

var_dump(array($a, $b, $c));

});

这代码比使用 Promisewrap 的又要简单了。这里,代码中的变量 $a, $b, $c 不再是 Promise 变量,而是实实在在的整数变量。也就是说,yield 把一个 Promise 变量变成了一个普通变量。

现在 Promiseco 中的代码已经被实实在在的同步化了。

现在你可能有新的疑问了,异步不是为了高效吗?现在把原本的异步代码同步化了,那还会高效吗?

当然,对这个例子上来说,效率肯定是没有提高,反而是严重降低的。甚至在这个例子中,最原始的那个形似异步的实现也不比同步实现更高效。因为在这个例子中,并没有涉及到并发和 IO 阻塞的情况。

下面我们就放到真实场景下来看看 Promise 和 co/yield 协程是怎么用的。

在 swoole 下使用 Promise 和 co/yield 协程

我们知道在 PHP 中,如果要让程序延时可以使用 sleep 函数(或者 usleep, time_nanosleep 函数)来让程序阻塞一会儿,但是这个阻塞会让整个进程都阻塞,所以在阻塞期间,什么都不能干。

下面我们来看看使用 swoole_timer_after 实现的延时执行:

require_once("Hprose.php");

use Hprose\Future;

date_default_timezone_set('UTC');

function wait($time) {

$wait = Future\promisify('swoole_timer_after');

for ($i = 0; $i < 5; $i++) {

yield $wait($time);

var_dump("wait ". ($time / 1000) . "s, now is " . date("H:i:s"));

}

}

Future\co(wait(2000));

Future\co(wait(1000));

该程序执行结果如下:

string(24) "wait 1s, now is 13:48:25"

string(24) "wait 2s, now is 13:48:26"

string(24) "wait 1s, now is 13:48:26"

string(24) "wait 1s, now is 13:48:27"

string(24) "wait 2s, now is 13:48:28"

string(24) "wait 1s, now is 13:48:28"

string(24) "wait 1s, now is 13:48:29"

string(24) "wait 2s, now is 13:48:30"

string(24) "wait 2s, now is 13:48:32"

string(24) "wait 2s, now is 13:48:34"

从结果中我们可以看出,wait(2000) 和 wait(1000) 各自都是顺序阻塞执行的,但是它们之间却是并发执行的。

也就是说,协程之间并不会相互阻塞,虽然这几个并发的协程是在同一个进程内跑的。

最后我们再来看一个用 co/yield 协程实现的并发抓图程序:

require_once("Hprose.php");

use Hprose\Promise;

function fetch($url) {

$dns_lookup = Promise\promisify('swoole_async_dns_lookup');

$writefile = Promise\promisify('swoole_async_writefile');

$url = parse_url($url);

list($host, $ip) = (yield $dns_lookup($url['host']));

$cli = new swoole_http_client($ip, isset($url['port']) ? $url['port'] : 80);

$cli->setHeaders([

'Host' => $host,

"User-Agent" => 'Chrome/49.0.2587.3',

]);

$get = Promise\promisify([$cli, 'get']);

yield $get($url['path']);

list($filename) = (yield $writefile(basename($url['path']), $cli->body));

echo "write $filename ok.\r\n";

$cli->close();

}

$urls = array(

'http://b.hiphotos.baidu.com/baike/c0%3Dbaike116%2C5%2C5%2C116%2C38/sign=5f4519ba037b020818c437b303b099b6/472309f790529822434d08dcdeca7bcb0a46d4b6.jpg',

'http://f.hiphotos.baidu.com/baike/c0%3Dbaike116%2C5%2C5%2C116%2C38/sign=1c37718b3cc79f3d9becec62dbc8a674/38dbb6fd5266d016dc2eaa5c902bd40735fa358a.jpg',

'http://h.hiphotos.baidu.com/baike/c0%3Dbaike116%2C5%2C5%2C116%2C38/sign=edd05c9c502c11dfcadcb771024e09b5/d6ca7bcb0a46f21f3100c52cf1246b600c33ae9d.jpg',

'http://a.hiphotos.baidu.com/baike/c0%3Dbaike92%2C5%2C5%2C92%2C30/sign=4693756e8094a4c21e2eef796f9d70b0/54fbb2fb43166d22df5181f5412309f79052d2a9.jpg',

'http://a.hiphotos.baidu.com/baike/c0%3Dbaike92%2C5%2C5%2C92%2C30/sign=9388507144a98226accc2375ebebd264/faf2b2119313b07eb2cc820c0bd7912397dd8c45.jpg',

);

foreach ($urls as $url) {

Promise\co(fetch($url));

}

在这个程序中,fetch 函数内的代码是同步执行的,但是多个 fetch 之间却是并发执行的,从结果输出就可以看出来,输出顺序是不一定的。但最后,你总能得到所有的美图。

总结:通过 swoole 跟 hprose 中的 Promise 和 co/yield 协程相结合,你可以方便的使用同步的方式来调用 swoole 中的异步函数和方法,并可以实现协程间的并发。

因为篇幅所限,这里无法把 hprose 中 Promise 和 co/yield 协程的全部内容都介绍完,如果你想了解更多,可以参考下面两篇内容:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/442926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php 获取agent,PHP代码 解析HTTP_USER_AGENT 获取客户端操作系统

*** 获取客户端操作系统信息包括win10* param null* author Jea杨* return string*/function GetOS(){$agent $_SERVER[HTTP_USER_AGENT];$os false;if (preg_match(/win/i, $agent) && strpos($agent, 95)){$os Windows 95;}else if (preg_match(/win 9x/i, $age…

php怎么写for循环,PHP for循环的写法和示例

For循环是最近的循环语句之一&#xff0c;无论哪种语言&#xff0c;都有这个循环语句&#xff0c;也是我们工作中常用的循环方法。语法规则&#xff1a;for (expr1; expr2; expr3){要执行的代码}expr1&#xff1a;表示循环开始的地方expr2 &#xff1a;循环的条件&#xff0c;如…

php批量下载网络图片,php批量下载网页图片并替换路径为本地

一篇文章复制过来&#xff0c;发现图片路径都是别人网站的&#xff0c;如何一键下载这些图片到本地&#xff0c;并且修改成为本地的路径呢。代码如下 复制代码/*** 获取替换文章中的图片路径* param string $xstr 内容 采集网页的content* param string $keyword 创建照片的文件…

php获取本机root,通过PHP执行root命令

慕村225694在尝试之前&#xff0c;请阅读整个文章&#xff0c;然后进行选择。使用二进制包装器(带有suid位)的解决方案1)创建一个脚本(最好是.sh)&#xff0c;其中包含要作为root用户运行的脚本。# cat > php_shell.sh < wrapper.c < #include #include int mai…

php位值,php中,如何取得一个整型值的低位和高位值?

整型值可以使用十进制&#xff0c;十六进制&#xff0c;八进制或二进制表示&#xff0c;前面可以加上可选的符号(- 或者 )。二进制表达的 integer 自 PHP 5.4.0 起可用。要使用八进制表达&#xff0c;数字前必须加上 0(零)。要使用十六进制表达&#xff0c;数字前必须加上 0x。…

java寂静岭 攻略,GBA版《寂静岭》HARRY篇图文流程攻略

“Play Novel: Silent Hill”是KONAMI于2001年3月21日在GBA上推出的一款文字冒险游戏&#xff0c;剧情内容取自同社的恐怖冒险游戏——Silent Hill(《寂静岭》)。游戏基本上是纯粹的文字冒险游戏&#xff0c;过程中穿插着几段动画CG作为过场&#xff0c;游戏中绝大部分的图片和…

php 实现百度坐标转换,PHP实现腾讯与百度坐标转换

废话不多说&#xff0c;直接上代码public function coordinate_switch($a,$b){//百度转腾讯坐标转换$x (double)$b - 0.0065;$y (double)$a - 0.006;$x_pi 3.14159265358979324;$z sqrt($x * $x$y * $y) - 0.00002 * sin($y * $x_pi);$theta atan2($y,$x) - 0.000003 * co…

实验一熟悉matlab环境,数字信号处理报告实验一:熟悉MATLAB环境.doc

数字信号处理报告实验一&#xff1a;熟悉MATLAB环境.doc实验一熟悉MATLAB环境一 实验目的1. 熟悉MATLAB的主要操作命令。2. 学会简单的矩阵输入和数据读写。3. 掌握简单的绘图命令。4. 用MATLAB编程并学会创建函数。5. 观察离散系统的频率响应。二 实验内容2.用MATLAB实现下列序…

MySQL中使用CASE出错,如何在MySQL中正确使用CASE..WHEN

如何在MySQL中正确使用CASE..WHEN这里是一个演示查询&#xff0c;注意它非常简单&#xff0c;仅在base_price为0的位置获取&#xff0c;并且仍然select条件3&#xff1a;SELECT CASE course_enrollment_settings.base_price WHEN course_enrollment_settings.base_price 0 THE…

matlab rootdir,Python cfg.ROOT_DIR属性代码示例

# 需要导入模块: from fast_rcnn.config import cfg [as 别名]# 或者: from fast_rcnn.config.cfg import ROOT_DIR [as 别名]def demo(net, image_name, classes):"""Detect object classes in an image using pre-computed object proposals.""&quo…

5g算法matlab怎么用,使用 MATLAB 开发 5G NR 设计

请选择其一AlabamaAlaska美属萨摩亚APO/FPO AAAPO/FPO AEAPO/FPO APArizonaArkansasCaliforniaCaroline IslandsColoradoConnecticutDelawareDistrict of ColumbiaFlorida格鲁吉亚关岛HawaiiIdahoIllinoisIndianaIowaKansasKentuckyLouisianaMaineMariana Islands马绍尔群岛Mar…

微擎cloud.mod.php,微擎“could not resolve ”、“could not resolve host”系列问题处理办法...

今天一个用户跟我说模块安装出现”could not resolve host:update.we10d.cn“报错&#xff0c;昨天在整理最近十个月用户搜索关键词排行的时候也发现”could not resolve host“、”could not resolve“还是占据不小的比例的。其实这类报错的处理方法大致相同&#xff0c;都是/…

php提交订单代码怎么写,提交代办订单示例代码

require curl.func.php;$appkey your_appkey_here;//你的appkey$illegalid 1333,1334;//违章ID$mobile 15158825888;$outorderno ;$appsecret your_appsecret_here;$queryarr array(mobile>$mobile,illegalid>$illegalid,outorderno>$outorderno);$sign makeSi…

基于matlab的信号与系统实例,华南理工大学信号与系统实验基于Matlab的信号处理实例...

第2讲基于Matlab的信号处理实例实验内容(1)(1) 读取给定的3D加速度信号文件&#xff0c;绘出信号波形&#xff1b;程序源代码&#xff1a;function sy2fid fopen(run 100m_TROUSERS POCKET_1_陈佳_1.txt,r);afscanf(fid,%d,%d,%d\n);fclose(fid);lenlength(a)/3;k1;for i1:len…

php qq接收不了,php imap接收qq邮件的问题

写一个接收邮件的代码&#xff0c;提示无法连接&#xff0c;qq邮箱已开启imap&#xff0c;下面贴上代码&#xff0c;麻烦各位帮忙看看&#xff1f;$host {imap.qq.com:993/imap/ssl}INBOX;$user *****;$pass *****;$inbox imap_open($host, $user, $pass)or die("cant…

oracle命令行打不开,关于oracle命令行上下左右键没法使用解决办法

使用rlwrap工具解决&#xff1a;sql1.下载安装readline和rlwrapvim//安装readlinebash[rootoracle opt]#wget ftp://ftp.gnu.org/gnu/readline/readline-6.2.tar.gzoracle[rootoracle opt]# tar xvf readline-6.2.tar.gzide[rootoracle opt]# cd readline-6.2工具[rootoracle r…

ora-01092: oracle 实例终止.强制断开连接,undo表空间故障特殊恢复(二)------ORA-01092: ORACLE 实例终止。强制断开连接...

原文出处&#xff1a;http://blog.csdn.net/wyzxg/archive/2010/09/10/5874726.aspxundo表空间故障特殊恢复(二)------ORA-01092: ORACLE 实例终止。强制断开连接这个测试的是instance recover(单实例里就是crash recovery)的恢复需要故障undo里的数据&#xff0c;一般的情况in…

php类如何变为静态调用,PHP类中静态方法如何调用非静态方法?_后端开发

php如何将字符串转成json_后端开发php将字符串转成json的方法是&#xff1a;可以通过json_encode()函数来实现。json_encode()函数用于对变量进行JSON编码&#xff0c;该函数如果执行成功&#xff0c;则返回JSON数据&#xff0c;反之则返回FALSE。PHP类中静态方法调用非静态方法…

java 简单获取Excel表格内容(初学者)

java 简单获取Excel表格内容&#xff08;初学者&#xff09; 代码&#xff1a; package cn.ccaih.read;import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.us…

PHP的注释标记是什么,html的注释标记是什么

html的注释标记是“”&#xff0c;用来在源文档中插入注释&#xff0c;注释的内容不会被显示在浏览器页面上。只有在文本编辑器中&#xff0c;或浏览器的“查看源代码”选项打开文档时&#xff0c;才能看到注释。本教程操作环境&#xff1a;windows7系统、HTML5版、Dell G3电脑…