Async介紹

2018-10-06 10:30 更新

Hack提供了一種稱為Async的功能,為您的程序提供了協(xié)作多任務(wù)的好處。它允許使用Async基礎(chǔ)架構(gòu)的代碼隱藏輸入/輸出(I / O)延遲和數(shù)據(jù)提取。因此,如果您的代碼具有涉及某種等待(例如,網(wǎng)絡(luò)訪問,等待數(shù)據(jù)庫查詢)的操作,則async將您的程序停止運(yùn)行的停機(jī)時間最小化,因?yàn)槌绦驅(qū)?zhí)行其他操作可能會在其他地方進(jìn)一步的I / O。

Async 不是多線程 - HHVM仍然在一個主要請求線程中執(zhí)行所有的PHP / Hack代碼 - 但是其他操作(例如MySQL查詢)現(xiàn)在可以執(zhí)行,而不需要在該代碼中使用的時間。

頁面作為依賴樹

想象一下,你有一個頁面包含兩個組件; 一個在MySQL中存儲數(shù)據(jù),另一個通過cURL從API獲取數(shù)據(jù),兩個緩存都導(dǎo)致Memcached。依賴關(guān)系可以這樣建模:

Async介紹

這樣的代碼結(jié)構(gòu)從Async中獲得最大的收益。

同步/阻塞IO:順序執(zhí)行

如果(像大多數(shù)PHP代碼)你不使用Async編程,每一步將一個接一個執(zhí)行:

Async介紹

Async執(zhí)行

所有PHP / Hack代碼在主請求線程中執(zhí)行,但是I / O不阻止它,并且多個I / O或其他Async任務(wù)可以同時執(zhí)行。如果您的代碼構(gòu)造為依賴樹并使用AsyncI / O,這將導(dǎo)致代碼的各個部分透明地交錯而不是彼此阻塞:

Async介紹

重要的是,您的代碼執(zhí)行的順序是不能保證的 - 例如,如果組件A的cURL請求速度較慢,則執(zhí)行相同的代碼可能會更像這樣:

Async介紹

以這種方式重新排序不同的任務(wù)指令,可以隱藏I / O 延遲。因此,當(dāng)一個任務(wù)當(dāng)前處于I / O指令(例如,等待數(shù)據(jù))時,另一個任務(wù)的指令,希望能夠減少延遲,可以在此期間執(zhí)行。

限制

兩個最重要的限制是:

  • 所有PHP / Hack代碼在主請求線程中執(zhí)行
  • 阻塞的API(例如mysql_query(),sleep())不要讓她自動轉(zhuǎn)換為Async功能-這將是不安全的,因?yàn)樗梢愿淖兊?,可能不能設(shè)計(jì)了這樣的可能性不相關(guān)的代碼的執(zhí)行順序。

例如,給出這個代碼:

<?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());

新用戶經(jīng)常認(rèn)為與多線程Async,所以期望do_cpu_work()并且do_sleep()并行執(zhí)行 - 但是,這不會發(fā)生,因?yàn)闆]有可以移動到后臺的操作:

  • do_cpu_work() 只包含沒有內(nèi)置的PHP代碼,所以執(zhí)行并阻止主請求線程
  • 雖然do_sleep()調(diào)用一個內(nèi)置的,它不是一個Async內(nèi)置的 - 所以它也必須阻止主請求線程

Async介紹

Async實(shí)踐: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 的調(diào)用curl_A()阻止了任何其他處理。因此,即使curl_B()是獨(dú)立的電話curl_A(),curl_A()在開始執(zhí)行之前也必須等待完成:

Async介紹

幸運(yùn)的是,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()允許調(diào)度程序在等待cURL的響應(yīng)時運(yùn)行其他代碼。最可能的行為是,當(dāng)我們還在等待調(diào)用時curl_B(),調(diào)度程序?qū)⑦x擇調(diào)用它,這反過來又會啟動另一個Asynccurl_exec()。由于HTTP請求通常較慢,因此主線程將處于空閑狀態(tài),直到其中一個請求完成:

Async介紹

這個執(zhí)行令是最有可能的,但不是保證; 例如,如果curl_B()請求比curl_A()HTTP請求快得多,curl_B()可以在完成之前curl_A()完成。

Async加速此示例的數(shù)量可能會有很大差異(例如,根據(jù)網(wǎng)絡(luò)條件和DNS緩存),但可能很重要:

Async

Total time taken: 0.74790596961975 seconds

Non-Async

Total time taken: 1.050155878067 seconds
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號