Async介绍
Hack提供了一种称为Async的功能,为您的程序提供了协作多任务的好处。它允许使用Async基础架构的代码隐藏输入/输出(I / O)延迟和数据提取。因此,如果您的代码具有涉及某种等待(例如,网络访问,等待数据库查询)的操作,则async将您的程序停止运行的停机时间最小化,因为程序将执行其他操作可能会在其他地方进一步的I / O。
Async 不是多线程 - HHVM仍然在一个主要请求线程中执行所有的PHP / Hack代码 - 但是其他操作(例如MySQL查询)现在可以执行,而不需要在该代码中使用的时间。
页面作为依赖树
想象一下,你有一个页面包含两个组件; 一个在MySQL中存储数据,另一个通过cURL从API获取数据,两个缓存都导致Memcached。依赖关系可以这样建模:
这样的代码结构从Async中获得最大的收益。
同步/阻塞IO:顺序执行
如果(像大多数PHP代码)你不使用Async编程,每一步将一个接一个执行:
Async执行
所有PHP / Hack代码在主请求线程中执行,但是I / O不阻止它,并且多个I / O或其他Async任务可以同时执行。如果您的代码构造为依赖树并使用AsyncI / O,这将导致代码的各个部分透明地交错而不是彼此阻塞:
重要的是,您的代码执行的顺序是不能保证的 - 例如,如果组件A的cURL请求速度较慢,则执行相同的代码可能会更像这样:
以这种方式重新排序不同的任务指令,可以隐藏I / O 延迟。因此,当一个任务当前处于I / O指令(例如,等待数据)时,另一个任务的指令,希望能够减少延迟,可以在此期间执行。
限制
两个最重要的限制是:
- 所有PHP / Hack代码在主请求线程中执行
- 阻塞的API(例如mysql_query(),sleep())不要让她自动转换为Async功能-这将是不安全的,因为它可以改变的,可能不能设计了这样的可能性不相关的代码的执行顺序。
例如,给出这个代码:
<?hh
namespace Hack\UserDocumentation\Async\Intro\Examples\Limtations;
async function do_cpu_work(): Awaitable<void> {
print("Start CPU work\n");
$a = 0;
$b = 1;
$list = [$a, $b];
for ($i = 0; $i < 1000; ++$i) {
$c = $a + $b;
$list[] = $c;
$a = $b;
$b = $c;
}
print("End CPU work\n");
}
async function do_sleep(): Awaitable<void> {
print("Start sleep\n");
sleep(1);
print("End sleep\n");
}
async function main(): Awaitable<void> {
print("Start of main()\n");
await \HH\Asio\v([
do_cpu_work(),
do_sleep(),
]);
print("End of main()\n");
}
\HH\Asio\join(main());
新用户经常认为与多线程Async,所以期望do_cpu_work()并且do_sleep()并行执行 - 但是,这不会发生,因为没有可以移动到后台的操作:
- do_cpu_work() 只包含没有内置的PHP代码,所以执行并阻止主请求线程
- 虽然do_sleep()调用一个内置的,它不是一个Async内置的 - 所以它也必须阻止主请求线程
Async实践:cURL
在没有Async的情况下使两个cURL请求的一种天真的方式可能如下所示:
<?hh
namespace Hack\UserDocumentation\Async\Intro\Examples\NonAsyncCurl;
function curl_A(): mixed {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.com/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
return curl_exec($ch);
}
function curl_B(): mixed {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.net/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
return curl_exec($ch);
}
function main(): void {
$start = microtime(true);
$a = curl_A();
$b = curl_B();
$end = microtime(true);
echo "Total time taken: " . strval($end - $start) . " seconds" . PHP_EOL;
}
main();
Output
Total time taken: 1.050155878067 seconds
在上面的示例中,对curl_exec()
in 的调用curl_A()
阻止了任何其他处理。因此,即使curl_B()
是独立的电话curl_A()
,curl_A()
在开始执行之前也必须等待完成:
幸运的是,HHVM提供了一个Async版本curl_exec()
:
<?hh
namespace Hack\UserDocumentation\Async\Intro\Examples\Curl;
async function curl_A(): Awaitable<string> {
$x = await \HH\Asio\curl_exec("http://example.com/");
return $x;
}
async function curl_B(): Awaitable<string> {
$y = await \HH\Asio\curl_exec("http://example.net/");
return $y;
}
async function async_curl(): Awaitable<void> {
$start = microtime(true);
list($a, $b) = await \HH\Asio\v(array(curl_A(), curl_B()));
$end = microtime(true);
echo "Total time taken: " . strval($end - $start) . " seconds" . PHP_EOL;
}
\HH\Asio\join(async_curl());
Output
Total time taken: 0.74790596961975 seconds
Async版本curl_exec()
允许调度程序在等待cURL的响应时运行其他代码。最可能的行为是,当我们还在等待调用时curl_B()
,调度程序将选择调用它,这反过来又会启动另一个Async
curl_exec()
。由于HTTP请求通常较慢,因此主线程将处于空闲状态,直到其中一个请求完成:
这个执行令是最有可能的,但不是保证; 例如,如果curl_B()请求比curl_A()HTTP请求快得多,curl_B()可以在完成之前curl_A()完成。
Async加速此示例的数量可能会有很大差异(例如,根据网络条件和DNS缓存),但可能很重要:
Async
Total time taken: 0.74790596961975 seconds
Non-Async
Total time taken: 1.050155878067 seconds