PHP 事件循环:异步编程的利器
核心要点
PHP 开发人员总是在等待某些事情。有时我们等待远程服务的请求。有时我们等待数据库返回来自复杂查询的行。如果我们可以在所有等待期间执行其他操作,那不是很好吗?
如果您编写过一些 JS 代码,您可能熟悉回调和 DOM 事件。虽然我们在 PHP 中也有回调,但它们的工作方式并不完全相同。这要归功于一个称为事件循环的功能。
我们将了解事件循环的工作原理,以及如何在 PHP 中使用事件循环。
我们将看到一些有趣的 PHP 库。有些人认为这些库还不够稳定,无法用于生产环境。有些人认为此处提供的示例“最好在更成熟的语言中实现”。尝试这些方法有很多很好的理由。在生产环境中避免这些方法也有很好的理由。这篇文章的目的是突出 PHP 中的可能性。
要理解事件循环,让我们看看它们在浏览器中的工作方式。看看这个例子:
function fitToScreen(selector) { var element = document.querySelector(selector); var width = element.offsetWidth; var height = element.offsetHeight; var top = "-" + (height / 2) + "px"; var left = "-" + (width / 2) + "px"; var ratio = getRatio(width, height); setStyles(element, { "position": "absolute", "left": "50%", "top": "50%", "margin": top + " 0 0 " + left, "transform": "scale(" + ratio + ", " + ratio + ")" }); } function getRatio(width, height) { return Math.min( document.body.offsetWidth / width, document.body.offsetHeight / height ); } function setStyles(element, styles) { for (var key in styles) { if (element.style.hasOwnProperty(key)) { element.style[key] = styles[key]; } } } fitToScreen(".welcome-screen");
这段代码不需要额外的库。它可以在任何支持 CSS 缩放转换的浏览器中工作。最新的 Chrome 版本就足够了。只需确保 CSS 选择器与文档中的元素匹配即可。
这几个函数接收一个 CSS 选择器,并将元素居中并缩放以适应屏幕。如果我们在 for 循环中抛出一个错误会发生什么?我们会看到类似这样的内容……
我们将该函数列表称为堆栈跟踪。这就是浏览器使用的堆栈内部的样子。它们将分步处理此代码……
这就像 PHP 如何使用堆栈来存储上下文一样。浏览器更进一步,为 DOM 事件和 Ajax 回调等内容提供 WebAPI。在其自然状态下,JavaScript 与 PHP 一样异步。也就是说:虽然两者看起来都可以同时执行许多操作,但它们都是单线程的。它们一次只能做一件事情。
使用浏览器 WebAPI(例如 setTimeout 和 addEventListener),我们可以将并行工作卸载到不同的线程。当这些事件发生时,浏览器将回调添加到回调队列中。当堆栈下次为空时,浏览器从回调队列中拾取回调并执行它们。
这个清除堆栈,然后是回调队列的过程,就是事件循环。
在 JS 中,我们可以运行以下代码:
function fitToScreen(selector) { var element = document.querySelector(selector); var width = element.offsetWidth; var height = element.offsetHeight; var top = "-" + (height / 2) + "px"; var left = "-" + (width / 2) + "px"; var ratio = getRatio(width, height); setStyles(element, { "position": "absolute", "left": "50%", "top": "50%", "margin": top + " 0 0 " + left, "transform": "scale(" + ratio + ", " + ratio + ")" }); } function getRatio(width, height) { return Math.min( document.body.offsetWidth / width, document.body.offsetHeight / height ); } function setStyles(element, styles) { for (var key in styles) { if (element.style.hasOwnProperty(key)) { element.style[key] = styles[key]; } } } fitToScreen(".welcome-screen");
当我们运行这段代码时,我们会在控制台中看到 outside the timeout,然后是 inside the timeout。setTimeout 函数是浏览器提供的 WebAPI 的一部分。当经过 1 毫秒后,它们会将回调添加到回调队列中。
第二个 console.log 在来自 inside the setTimeout 的 console.log 开始之前完成。我们在标准 PHP 中没有类似 setTimeout 的东西,但如果我们必须尝试模拟它:
setTimeout(function() { console.log("inside the timeout"); }, 1); console.log("outside the timeout");
当我们运行它时,我们会看到 inside the timeout,然后是 outside the timeout。这是因为我们必须在 setTimeout 函数中使用无限循环才能在延迟后执行回调。
将 while 循环移到 setTimeout 之外并将所有代码包含在其中可能很诱人。这可能会使我们的代码感觉不那么阻塞,但在某些时候,我们总是会被该循环阻塞。在某些时候,我们将看到我们一次只能在一个线程中做一件事情。
虽然标准 PHP 中没有任何类似 setTimeout 的东西,但有一些晦涩的方法可以实现与事件循环并行的非阻塞代码。我们可以使用 stream_select 等函数来创建非阻塞网络 IO。我们可以使用像 EIO 这样的 C 扩展来创建非阻塞文件系统代码。让我们来看看基于这些晦涩方法构建的库……
Icicle 是一个考虑了事件循环的组件库。让我们看一个简单的例子:
function setTimeout(callable $callback, $delay) { $now = microtime(true); while (true) { if (microtime(true) - $now > $delay) { $callback(); return; } } } setTimeout(function() { print "inside the timeout"; }, 1); print "outside the timeout";
这是使用 icicleio/icicle 版本 0.8.0
Icicle 的事件循环实现很棒。它还有许多其他令人印象深刻的功能;例如 A promise、socket 和服务器实现。
Icicle 还使用生成器作为协程。生成器和协程是一个不同的主题,但它们允许的代码很漂亮:
use Icicle\Loop; Loop\timer(0.1, function() { print "inside timer"; }); print "outside timer"; Loop\run();
这是使用 icicleio/dns 版本 0.5.0 生成器使编写类似于同步代码的异步代码更容易。当与 promise 和事件循环结合使用时,它们会产生像这样的优秀非阻塞代码!
ReactPHP 具有类似的事件循环实现,但没有所有有趣的生成器内容:
function fitToScreen(selector) { var element = document.querySelector(selector); var width = element.offsetWidth; var height = element.offsetHeight; var top = "-" + (height / 2) + "px"; var left = "-" + (width / 2) + "px"; var ratio = getRatio(width, height); setStyles(element, { "position": "absolute", "left": "50%", "top": "50%", "margin": top + " 0 0 " + left, "transform": "scale(" + ratio + ", " + ratio + ")" }); } function getRatio(width, height) { return Math.min( document.body.offsetWidth / width, document.body.offsetHeight / height ); } function setStyles(element, styles) { for (var key in styles) { if (element.style.hasOwnProperty(key)) { element.style[key] = styles[key]; } } } fitToScreen(".welcome-screen");
这是使用 react/event-loop 版本 0.4.1
ReactPHP 比 Icicle 更成熟,并且具有更广泛的组件。Icicle 还有很长的路要走才能与 ReactPHP 提供的所有功能竞争。不过,开发人员正在取得良好的进展!
摆脱我们被教导要拥有的单线程思维方式是困难的。如果我们可以访问非阻塞 API 和事件循环,我们就不知道我们可以编写多少代码。
PHP 社区需要了解这种架构。我们需要学习和试验异步和并行执行。我们需要从拥有事件循环多年的其他语言中窃取这些概念和最佳实践,直到“如何有效地使用最多的系统资源?”成为一个用 PHP 容易回答的问题。
敬请期待即将推出的 Icicle 的更实际的实现!
PHP 中的事件循环是一种编程结构,用于等待和调度程序中的事件或消息。它通过跟踪响应外部刺激的每个活动事件并在它们完成时调度它们来工作。这在 PHP 中尤其适用于处理异步操作,在这些操作中,您希望启动操作然后继续处理而无需等待操作完成。
传统的 PHP 编程是同步的,这意味着它一次执行一个操作,按照编写的顺序执行,并且必须等待每个操作完成才能继续执行下一个操作。另一方面,事件循环允许异步编程。这意味着可以启动操作,然后将其搁置,直到结果准备就绪,在此期间可以执行其他操作。
在您的 PHP 应用程序中实现事件循环涉及使用提供此功能的库,例如 ReactPHP 或 Amp。这些库提供了创建和管理事件循环的必要接口和类。然后,您可以使用此事件循环来处理应用程序中的异步任务。
在 PHP 中使用事件循环可以大大提高应用程序的性能和响应能力。它允许您同时处理多个任务,而不是顺序处理,这可以导致更好地利用资源并缩短响应时间。这在需要处理大量并发连接的应用程序(例如聊天服务器或实时数据馈送)中尤其有利。
虽然事件循环可以提供显著的性能优势,但它们也会使应用程序的复杂性增加。它们需要不同的编程风格,并且可能使代码更难以理解和调试。此外,并非所有任务都适合异步处理,有些任务在事件循环中可能更难实现。
是的,可以将事件循环与 Laravel 或 Symfony 等 PHP 框架一起使用,尽管这可能需要一些额外的配置。这两个框架都设计用于与同步 PHP 代码一起工作,但它们可以适应与事件循环一起工作以处理异步任务。
事件循环中的错误处理可能比同步 PHP 代码中的错误处理更复杂。由于任务是异步执行的,因此可能无法立即捕获错误。相反,您通常需要提供一个回调函数,该函数将在发生错误时被调用。
是的,您可以在 PHP CLI(命令行界面)脚本中使用事件循环。事实上,这是事件循环的常见用例,因为 CLI 脚本通常需要同时执行多个任务。
PHP 的垃圾收集独立于事件循环工作。但是,由于事件循环会保留对所有活动任务的引用,因此这些任务只有在完成之后才会被垃圾收集。这意味着您需要注意避免事件循环代码中的内存泄漏。
是的,您可以将事件循环与 PHP 的内置服务器一起使用。但是,请记住,内置服务器并非设计用于生产用途,并且可能无法提供与专用 Web 服务器相同的性能或可靠性级别。
以上是PHP中的事件循环的介绍的详细内容。更多信息请关注PHP中文网其他相关文章!