symplely / coroutine
使用生成器的协作式多任务。协程、async 和 await 的基础知识!
Requires
- php: >7.1
- symplely/spawn: ^1.1.8
Requires (Dev)
- phpunit/phpunit: ^6 | ^7 | ^8
Suggests
- ext-uv: >0.2.4
README
这是版本 2x,它打破了版本 1x,通过将所有必需的 CONSTANTS 移动到依赖包中,更改了 namespace 中全局函数的基本设置。本版本包括新的 Fiber 实现,无需 PHP ext-fibers 扩展,计划用于 PHP 8.1。所有 ext-fibers
测试 和 示例 都已在本处实现,具体为 examples/fiber、tests/fiber 和 tests/FiberTest.php。
为了获得最佳性能,建议安装跨平台的 libuv 库和 PHP 扩展 ext-uv。请参阅在线 书籍,以了解其使用的完整教程概述。
所有 libuv
socket/stream/udp/tcp
类似功能都需要为 Windows 重新实现,之前的假设它们已损坏,情况并非如此,而是由于使用的 PHPext-uv
版本的 libuv 和对功能应如何工作的假设存在问题。因此,当前使用原生stream_select
为 Windows 实现的方案将被重构/重构。
此包的下一个版本(在 2x 发布之后),将需要 uv-ffi,这是 libuv 的 ext-uv 的 FFI 版本,因此无需下载/安装额外的二进制库包,并且一些当前依赖项将不再必要。
要了解基本概念概述,请阅读 "Concurrency and PHP in relation to modern programming languages, Python, Go, NodeJS, Rust, Etc",目前为 草稿 形式,完成时将在 dev.io 上发布。
本版本还实现了 PHP ext-parallel 扩展,其方式使用了 libuv 的子 进程 功能。要快速比较 线程 和 进程 的性能,请参阅 Launching Linux threads and processes with clone。
此处实现的遵循 parallel\Runtime、parallel\Future、parallel\Channel 和 Functional API 规范,但没有任何 限制。
通过使用opis/closure软件包来克服这些限制。所有ext-parallel扩展的测试和示例,已经在此处examples/parallel、tests/parallel中进行主要修改,包括此软件包库并删除单数组参数到可变数组的要求。有些与原来有较大差异的测试添加了文件名中的.1
,一些可能需要yield
才能正确使用的测试被跳过了。
ext-parallel的events
和sync
API尚未实现,没有看到用例,并且已经作为此协程软件包的内部部分。
目录
简介
什么是异步?简单地说,是避免下一条指令的阻塞。这是捕获和稍后运行的机制。
这个软件包解决了当前PHP实现中的哪些问题? 易用性。 Amp和ReactPHP都提供了异步结果。但两者都需要用户/开发者进行更多设置才能运行简单的代码行。还有Swoole和Hhvm,但它们都不是标准的PHP安装。
当使用Amp或ReactPHP及其一些基于它们的软件包时,您不仅必须管理您提供的回调
,还要管理返回的Promise
对象和事件循环
对象。这些库模仿了旧的JavaScript,而现在的JavaScript
正朝着简单的async/await
语法发展。这引发了一个问题。
易用性是什么意思?我们可以先回顾其他编程语言的实现。事实上,您已经有一个正在运行的应用程序,您希望各个部分运行得更灵敏,这意味着要做更多。
Go有它的goroutine关键字go
,然后是您的函数和语句。
以下链接详细介绍了Python、Ruby、Rust、Nim、C#、Elixir、Java和C++中的async/await
关键字与函数和语句的组合。
使用它们时,它们会返回您实际想要的结果,无需任何对象,无需回调,并且调用一切之后,所有内容都会继续运行。
关于这些语言,其历史、实际行为和底层概念都是相同的,调用结构简单。它们都以各种方式引用了yield
或迭代器
的使用,该软件包依赖于这些,并使得以下内容成为可能。
要使您的代码异步,您只需在代码中放置yield
,并在调用您的代码之前用yield
作为前缀,以获得您想要的实际结果
。
通过使用此软件包,您将获得一个使用yield
的PHP版本的async/await
。
本程序提供了一些辅助函数来整合一切。主要有 away()
函数,类似于 Python 的 create_task(),其行为类似于 Google 的 go() 关键字,这里作为别名函数 go()
包含在内。
使用它后,会立即返回一个数字,该数字可以与另一个类似 Python 的函数 gather() 一起使用,该函数也类似于 Google 的 WaitGroup。这将等待并返回远程/分离代码在 background
中运行的结果。
此包遵循新的范式 行为编程,其概念为 B-threads,功能生成器。
Swoole 协程 和 FaceBook 的 Hhvm PHP 的基本整体使用遵循与其他类似的实现方式,并在此提出。
为了进一步说明,可以参考 Python 与 Node.js 的异步并发入门 中对 NodeJS 和 Python 的比较。
// async_scrape.js (tested with node 11.3) const sleep = ts => new Promise(resolve => setTimeout(resolve, ts * 1000)); async function fetchUrl(url) { console.log(`~ executing fetchUrl(${url})`); console.time(`fetchUrl(${url})`); await sleep(1 + Math.random() * 4); console.timeEnd(`fetchUrl(${url})`); return `<em>fake</em> page html for ${url}`; } async function analyzeSentiment(html) { console.log(`~ analyzeSentiment("${html}")`); console.time(`analyzeSentiment("${html}")`); await sleep(1 + Math.random() * 4); const r = { positive: Math.random() } console.timeEnd(`analyzeSentiment("${html}")`); return r; } const urls = [ "https://www.ietf.org/rfc/rfc2616.txt", "https://en.wikipedia.org/wiki/Asynchronous_I/O", ] const extractedData = {} async function handleUrl(url) { const html = await fetchUrl(url); extractedData[url] = await analyzeSentiment(html); } async function main() { console.time('elapsed'); await Promise.all(urls.map(handleUrl)); console.timeEnd('elapsed'); } main()
# async_scrape.py (requires Python 3.7+) import asyncio, random, time async def fetch_url(url): print(f"~ executing fetch_url({url})") t = time.perf_counter() await asyncio.sleep(random.randint(1, 5)) print(f"time of fetch_url({url}): {time.perf_counter() - t:.2f}s") return f"<em>fake</em> page html for {url}" async def analyze_sentiment(html): print(f"~ executing analyze_sentiment('{html}')") t = time.perf_counter() await asyncio.sleep(random.randint(1, 5)) r = {"positive": random.uniform(0, 1)} print(f"time of analyze_sentiment('{html}'): {time.perf_counter() - t:.2f}s") return r urls = [ "https://www.ietf.org/rfc/rfc2616.txt", "https://en.wikipedia.org/wiki/Asynchronous_I/O", ] extracted_data = {} async def handle_url(url): html = await fetch_url(url) extracted_data[url] = await analyze_sentiment(html) async def main(): t = time.perf_counter() await asyncio.gather(*(handle_url(url) for url in urls)) print("> extracted data:", extracted_data) print(f"time elapsed: {time.perf_counter() - t:.2f}s") asyncio.run(main())
以这个包作为起点,它具有相同的简单性。
// This is in the examples folder as "async_scrape.php" include 'vendor/autoload.php'; function fetch_url($url) { print("~ executing fetch_url($url)" . \EOL); \timer_for($url); yield \sleep_for(\random_uniform(1, 5)); print("time of fetch_url($url): " . \timer_for($url) . 's' . \EOL); return "<em>fake</em> page html for $url"; }; function analyze_sentiment($html) { print("~ executing analyze_sentiment('$html')" . \EOL); \timer_for($html . '.url'); yield \sleep_for(\random_uniform(1, 5)); $r = "positive: " . \random_uniform(0, 1); print("time of analyze_sentiment('$html'): " . \timer_for($html . '.url') . 's' . \EOL); return $r; }; function handle_url($url) { yield; $extracted_data = []; $html = yield fetch_url($url); $extracted_data[$url] = yield analyze_sentiment($html); return yield $extracted_data; }; function main() { $urls = [ "https://www.ietf.org/rfc/rfc2616.txt", "https://en.wikipedia.org/wiki/Asynchronous_I/O" ]; $urlID = []; \timer_for(); foreach ($urls as $url) $urlID[] = yield \away(handle_url($url)); $result_data = yield \gather($urlID); foreach ($result_data as $id => $extracted_data) { echo "> extracted data:"; \print_r($extracted_data); } print("time elapsed: " . \timer_for() . 's'); } \coroutine_run(main());
尝试用其他纯 PHP 异步实现重新创建这个,它们首先需要重写才能接近。
这里的 协程 是特别设计的基于 生成器 的函数,使用 yield
和 yield from
。当使用时,它们 控制上下文,这意味着 捕获/释放
应用程序的执行流程。
当 yield
放置在代码块内时,它会向调用函数指示,将返回一个对象而不是立即执行代码。
此包代表调用函数,一个 调度器,类似于 事件循环。协程需要被调度以运行,一旦调度,协程就被包装在一个 Task
中,这是一个 Promise 类型。
task
是一个表示一些要执行的工作的对象,可能最终会有一个结果。这些任务被 注册 在一个负责运行它们的调度器上。
由于 PHP(没有扩展的情况下)的 单线程 特性,我们不能将 task
视为一个执行单个 长运行 计算的操作——这将 阻塞 单一线程,直到任务完成。
相反,tasks
必须尽可能地以小块/迭代 ('ticks') 执行工作,并在适当的时候将控制权交回给调度器。这被称为 协作多任务处理(之所以称为协作多任务处理,是因为任务必须通过自愿释放控制权来协作)。
调度器负责对计划的任务进行“tick”,每个计划的任务都会重复“tick”直到完成。调度器实现如何以允许所有计划的任务运行的方式进行,由调度器实现来决定。
一个 task
可以通过以下三种方式之一完成
The task reaches successful completion, and optionally produces a result
The task encounters an error and fails
The task is cancelled by calling cancel()
当使用此包,并且你正在工作的代码包含 yield
点时,这些点定义了如果存在其他任务,可以发生 上下文切换 的位置,如果没有其他任务,则不会发生。这也可以视为 断点/traps,就像使用调试器时一样,当触发时,调试器介入,你可以查看状态并执行剩余的代码。
上下文切换 代表 调度器 将控制流从 协程 之一切换到另一个。
在此处,协程被定义为包含
yield
关键字的功能/方法,它将返回 生成器 对象。
立即返回的 生成器 对象为我们提供了访问少数方法的机会,这些方法允许它自己进行进展。
因此,在这里,我们有一个非常特殊的情况,即 生成器
是 PHP 语言的一部分,当我们通过 Promise 的工作方式来看时,它不阻塞,只执行行并返回。这是异步的主要思想。
Promises 返回一个对象,该对象被放入事件循环队列。事件循环执行附加到对象上的回调的实际执行。这实际上是一个手动过程,需要管理大量的代码状态/开销。这被称为反应器模式的执行,同步分配回调。
事件循环的机制在启动一个 生成器 时就已经存在了。我认为这是一个 Proactor 模式。由于 yield
的动作是发起者,开始检查资源可用性,在那一刻执行操作/动作,并处理/返回完成事件,所有这些都是异步的。
阅读这篇帖子,C++20 中的协程是什么?
There are two kinds of coroutines; stackful and stackless.
A stackless coroutine only stores local variables in its state and its location of execution.
A stackful coroutine stores an entire stack (like a thread).
Stackless coroutines can be extremely light weight. The last proposal I read involved basically rewriting your function into something a bit like a lambda; all local variables go into the state of an object, and labels are used to jump to/from the location where the coroutine "produces" intermediate results.
The process of producing a value is called "yield", as coroutines are bit like cooperative multithreading; you are yielding the point of execution back to the caller.
此包执行协作调度,多任务和异步编程的基础。
当引入 yield
时发生的步骤。
- 现在这个 函数 是一个
协程
。 - 返回的对象被
调度器
捕获。 - 调度器将这个捕获的
生成器
对象包装在一个任务
对象中。 - 任务对象具有额外的方法和功能,它可以看作是类似
promise
的。 - 现在将任务放入由
调度器
控制的任务队列
。 - 您运行您的
函数
,使一切开始运动。 在这里您并没有启动任何 事件循环。这可以被看作是事件循环,是调度器在将任务放入行动之前或之后所做的工作。 - 这个
任务
会落在哪里/返回哪里? 答案:调用它的相同位置,这里没有 回调。
步骤 1 在其他语言中通过特定的关键字
async
实现。
步骤 2 到 6 在其他语言中通过特定的关键字
await
实现。
这里使用的术语/命名更符合 Python 的 Asyncio 和 Curio 的使用。实际上,大多数源代码方法调用已经被更改为匹配这些。
这个包应该被看作/用作 用户级 扩展,它的 yield
使用已经被 RFC 创造者所设想。
函数
应该只使用此处和 Core.php 文件中定位到的函数。直接访问对象类库是不推荐的,名称可能会更改,或者如果没有在这里列出,可能会完全删除。第三方库包开发是例外。
与 网络 相关的功能在 Stream.php 中,文件系统 在 Path.php 中,以及 进程 在 Worker.php 中,都已经进行了命名空间处理,因此使用方式如下
use function Async\Path\{ , }; use function Async\Worker\{ , }; use function Async\Stream\{ , };
/** * Returns a random float between two numbers. * * Works similar to Python's `random.uniform()` * @see https://docs.pythonlang.cn/3/library/random.html#random.uniform */ \random_uniform($min, $max); /** * Return the value (in fractional seconds) of a performance counter. * Using either `hrtime` or system's `microtime`. * * The $tag is: * - A reference point used to set, to get the difference between the results of consecutive calls. * - Will be cleared/unset on the next consecutive call. * * returns float|void * * @see https://docs.pythonlang.cn/3/library/time.html#time.perf_counter * @see https://node.org.cn/docs/latest-v11.x/api/console.html#console_console_time_label */ \timer_for(string $tag = 'perf_counter'); /** * Makes an resolvable function from label name that's callable with `away` */ \async(string $labelFunction, $asyncFunction); /** * Wrap the value with `yield`, when placed within this insure that * any *function/method* will be `awaitable` and the actual return * value is picked up properly by `gather()`. */ return \value($value) /** * Add/schedule an `yield`-ing `function/callable/task` for background execution. * Will immediately return an `int`, and continue to the next instruction. * Returns an task Id * - This function needs to be prefixed with `yield` * * @see https://docs.pythonlang.cn/3.7/library/asyncio-task.html#asyncio.create_task */ yield \away($awaitedFunction, ...$args) ; /** * Performs a clean application exit and shutdown. * * Provide $skipTask incase called by an Signal Handler. Defaults to the main parent task. * - Use `get_task()` to retrieve caller's task id. * * - This function needs to be prefixed with `yield` */ yield \shutdown($skipTask) /** * Wrap the callable with `yield`, this insure the first attempt to execute will behave * like a generator function, will switch at least once without actually executing, return object instead. * This function is used by `away` not really called directly. * * @see https://docs.pythonlang.cn/3.7/library/asyncio-task.html#awaitables */ \awaitAble($awaitableFunction, ...$args); /** * Run awaitable objects in the tasks set concurrently and block until the condition specified by race. * * Controls how the `gather()` function operates. * `gather_wait` will behave like **Promise** functions `All`, `Some`, `Any` in JavaScript. * * - This function needs to be prefixed with `yield` * * @see https://docs.pythonlang.cn/3.7/library/asyncio-task.html#waiting-primitives */ yield \gather_wait(array $tasks, int $race = 0, bool $exception = true, bool $clear = true) /** * Run awaitable objects in the taskId sequence concurrently. * If any awaitable in taskId is a coroutine, it is automatically scheduled as a Task. * * If all awaitables are completed successfully, the result is an aggregate list of returned values. * The order of result values corresponds to the order of awaitables in taskId. * * The first raised exception is immediately propagated to the task that awaits on gather(). * Other awaitables in the sequence won't be cancelled and will continue to run. * - This function needs to be prefixed with `yield` * * @see https://docs.pythonlang.cn/3.7/library/asyncio-task.html#asyncio.gather */ yield \gather(...$taskId); /** * Block/sleep for delay seconds. * Suspends the calling task, allowing other tasks to run. * A result is returned If provided back to the caller * - This function needs to be prefixed with `yield` */ yield \sleep_for($delay, $result); /** * Creates an communications Channel between coroutines, returns an object * Similar to Google Go language - basic, still needs additional functions * - This function needs to be prefixed with `yield` */ yield \make(); /** * Send message to an Channel * - This function needs to be prefixed with `yield` */ yield \sender($channel, $message, $taskId); /** * Set task as Channel receiver, and wait to receive Channel message * Will continue other tasks until so. * - This function needs to be prefixed with `yield` */ yield \receiver($channel); /** * A goroutine is a function that is capable of running concurrently with other functions. * To create a goroutine we use the keyword `go` followed by a function invocation * @see https://www.golang-book.com/books/intro/10#section1 */ yield \go($goFunction, ...$args); /** * Modeled as in `Go` Language. * * The behavior of defer statements is straightforward and predictable. * There are three simple rules: * 1. *A deferred function's arguments are evaluated when the defer statement is evaluated.* * 2. *Deferred function calls are executed in Last In First Out order after the* surrounding function returns. * 3. *Deferred functions can`t modify return values when is type, but can modify content of reference to * * @see https://golang.ac.cn/doc/effective_go.html#defer */ \defer(&$previous, $callback) /** * Modeled as in `Go` Language. * * Regains control of a panicking `task`. * * Recover is only useful inside `defer()` functions. During normal execution, a call to recover will return nil * and have no other effect. If the current `task` is panicking, a call to recover will capture the value given * to panic and resume normal execution. */ \recover(&$previous, $callback); /** * Modeled as in `Go` Language. * * An general purpose function for throwing an Coroutine `Exception`, * or some abnormal condition needing to keep an `task` stack trace. */ \panic($message, $code, $previous); /** * Return the task ID * - This function needs to be prefixed with `yield` */ yield \get_task(); /** * kill/remove an task using task id * - This function needs to be prefixed with `yield` */ yield \cancel_task($tid); /** * Wait for the callable/task to complete with a timeout. * Will continue other tasks until so. * - This function needs to be prefixed with `yield` */ yield \wait_for($callable, $timeout); /** * Wait on read stream/socket to be ready read from. * Will continue other tasks until so. * - This function needs to be prefixed with `yield` */ yield \read_wait($stream); /** * Wait on write stream/socket to be ready to be written to. * Will continue other tasks until so. * - This function needs to be prefixed with `yield` */ yield \write_wait($stream); /** * Wait on keyboard input. * Will continue other tasks until so. * Will not block other task on `Linux`, will continue other tasks until `enter` key is pressed, * Will block on Windows, once an key is typed/pressed, will continue other tasks `ONLY` if no key is pressed. * - This function needs to be prefixed with `yield` */ yield \input_wait($size); /** * An PHP Functional Programming Primitive. * Return a curryied version of the given function. You can decide if you also * want to curry optional parameters or not. * * @see https://github.com/lstrojny/functional-php/blob/master/docs/functional-php.md#currying */ \curry($function, $required); \coroutine_instance(); \coroutine_clear(); \coroutine_create($coroutine); /** * This function runs the passed coroutine, taking care of managing the scheduler and * finalizing asynchronous generators. It should be used as a main entry point for programs, and * should ideally only be called once. * * @see https://docs.pythonlang.cn/3.7/library/asyncio-task.html#asyncio.run */ \coroutine_run($coroutine);
use function Async\Worker\{ add_process, spawn_task, spawn_await }; /** * Add/execute a blocking `subprocess` task that runs in parallel. * This function will return `int` immediately, use `gather()` to get the result. * - This function needs to be prefixed with `yield` */ yield \spawn_task($command, $timeout); /** * Add and wait for result of an blocking `I/O` subprocess that runs in parallel. * - This function needs to be prefixed with `yield` * * @see https://docs.pythonlang.cn/3.7/library/asyncio-subprocess.html#subprocesses * @see https://docs.pythonlang.cn/3.7/library/asyncio-dev.html#running-blocking-code */ yield \spawn_await($callable, $timeout, $display, $channel, $channelTask, $signal, $signalTask); /** * Add and wait for result of an blocking `I/O` subprocess that runs in parallel. * This function turns the calling function internal __state/type__ used by `gather()` * to **process/paralleled** which is handled differently. * - This function needs to be prefixed with `yield` * * @see https://docs.pythonlang.cn/3.7/library/asyncio-subprocess.html#subprocesses * @see https://docs.pythonlang.cn/3.7/library/asyncio-dev.html#running-blocking-code */ yield \add_process($command, $timeout);
use function Async\Path\file_***Any File System Command***; /** * Executes a blocking system call asynchronously either natively thru `libuv`, `threaded`, or it's `uv_spawn` * feature, or in a **child/subprocess** by `proc_open`, if `libuv` is not installed. * - This function needs to be prefixed with `yield` */ yield \file_***Any File System Command***( ...$arguments);
安装
composer require symplely/coroutine
本版本将在可用的情况下使用 libuv 功能。执行以下操作之一进行安装。
对于类似于 Debian 的发行版,如 Ubuntu...
apt-get install libuv1-dev php-pear php-dev -y
对于类似于 RedHat 的发行版,如 CentOS...
yum install libuv-devel php-pear php-dev -y
现在有 Pecl 自动编译、安装和设置。
pecl channel-update pecl.php.net pecl install uv-beta
对于 Windows,有一个好消息,通过 libuv
原生 async 已经到来。
稳定 PHP 版本的 Windows 构建可以从 PECL 获取。
直接从 https://windows.php.net/downloads/pecl/releases/uv/0.2.4/ 下载最新版本。
将 libuv.dll
提取到样本目录作为 PHP
二进制可执行文件,并将 php_uv.dll
提取到 ext\
目录。
在 php.ini 中启用扩展 php_sockets.dll
和 php_uv.dll
。
cd C:\Php Invoke-WebRequest "https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.2-nts-vc15-x64.zip" -OutFile "php_uv-0.2.4.zip" #Invoke-WebRequest "https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.3-nts-vc15-x64.zip" -OutFile "php_uv-0.2.4.zip" #Invoke-WebRequest "https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.4-ts-vc15-x64.zip" -OutFile "php_uv-0.2.4.zip" 7z x -y php_uv-0.2.4.zip libuv.dll php_uv.dll copy php_uv.dll ext\php_uv.dll del php_uv.dll del php_uv-0.2.4.zip echo extension=php_sockets.dll >> php.ini echo extension=php_uv.dll >> php.ini
注意:在使用
uv_spawn
时,Windows 和 Linux 上的 PHP ZTS 似乎存在问题。
用法
一般来说,任何带有 yield
关键字的函数/方法,都将作为中断点操作,挂起当前例程,执行其他操作,然后返回/恢复。
function main() { // Your initialization/startup code will need to be enclosed inside an function. // This is required for proper operations to start. } \coroutine_run(\main());
之后,请查看以下 examples 文件夹中的脚本。
/** * @see https://docs.pythonlang.cn/3/library/asyncio-task.html#timeouts */ include 'vendor/autoload.php'; function eternity() { // Sleep for one hour print("\nAll good!\n"); yield \sleep_for(3600); print(' yay!'); } function keyboard() { // will begin outputs of `needName` in 1 second print("What's your name: "); // Note: I have three Windows systems // - Windows 10 using PHP 7.2.18 (cli) (built: Apr 30 2019 23:32:39) ( ZTS MSVC15 (Visual C++ 2017) x64 ) // - Windows 10 using PHP 7.1.19 (cli) (built: Jun 20 2018 23:37:54) ( NTS MSVC14 (Visual C++ 2015) x86 ) // - Windows 7 using PHP 7.1.16 (cli) (built: Mar 28 2018 21:15:31) ( ZTS MSVC14 (Visual C++ 2015) x64 ) // Windows 10 blocks STDIN from the beginning with no key press. // Windows 7 does non-blocking STDIN, if no input attempted. only after typing something it blocks. return yield \input_wait(); } function needName() { $i = 1; yield \sleep_for(1); while(true) { echo $i; yield \sleep_for(0.05); $i++; if ($i == 15) { print(\EOL.'hey! try again: '); } if ($i == 100) { print(\EOL.'hey! try again, one more time: '); break; } } } function main() { yield \away(\needName()); echo \EOL.'You typed: '.(yield \keyboard()).\EOL; try { // Wait for at most 0.5 second yield \wait_for(\eternity(), 0.5); } catch (\RuntimeException $e) { print("\ntimeout!"); // this script should have exited automatically, since // there are no streams open, nor tasks running, this exception killed `eternity` task // currently, will continue to run // task id 2 is `ioWaiting` task, the scheduler added for listening // for stream socket connections yield \cancel_task(2); // This might just be because `main` is task 1, // and still running by the exception throw, need more testing } } \coroutine_run(\main());
/** * @see https://golangbot.com/goroutines/ * @see https://play.golang.org/p/oltn5nw0w3 */ include 'vendor/autoload.php'; function numbers() { for ($i = 1; $i <= 5; $i++) { yield \sleep_for(250 * \MS); print(' '.$i); } } function alphabets() { for ($i = 'a'; $i <= 'e'; $i++) { yield \sleep_for(400 * \MS); print(' '.$i); } } function main() { yield \go(\numbers()); yield \go(\alphabets()); yield \sleep_for(3000 * \MS); print(" main terminated"); } \coroutine_run(\main());
开发
/** * Template for developing an library package for access */ public static function someName($whatever, ...$args) { return new Kernel( function(TaskInterface $task, Coroutine $coroutine) use ($whatever, $args){ // Use/Execute/call some $whatever with ...$args; // if ($done) { // will return $someValue back to the caller $task->sendValue($someValue); // will return back to the caller, the callback $coroutine->schedule($task); } } ); } // Setup to call function some_name($whatever, ...$args) { return Kernel::someName($whatever, ...$args); } // To use yield \some_name($whatever, ...$args);
待办事项
- 添加
WebSocket
支持,或者真正转换/重写某些包。 - 添加
数据库
支持,基于我对 ezsql 的维护。 - 添加更多来自其他语言的标准化示例,并转换为其他语言。
- 根据 Python、Go 或任何其他语言中类似的功能部分更新文档。
致谢/参考文献
尼基塔·波波夫 关于 在 PHP 中使用协程进行协同多任务处理。此包 分支 了 Ditaio,重构/重写。
Parallel 类是对 spatie/async 的重构/重写。Parallel 类依赖于 symplely/spawn 作为依赖项,用于 子进程 管理/执行,它使用 libuv 的 uv_spawn
来启动进程。该 Spawn 包依赖于 opis/closure,用于克服 PHP 序列化限制,并且作为回退使用 symfony/process 来启动进程,如果未安装 libuv 的 PHP-UV 扩展。
贡献
鼓励并欢迎贡献;我总是很高兴在 Github 上收到反馈或拉取请求 :) 为错误和新功能创建 Github Issues 并对您感兴趣的问题进行评论。
许可
MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件。