uppes / coroutine
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 版本,因为全局函数的 命名空间,将所有必需的 常量 移动到依赖包中。本版本包含新的 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,未发现使用案例,并且这些功能已经作为此 coroutine 包的内部部分。
目录
简介
什么是异步? 简单地说,避免下一个指令的阻塞。这是一种 捕获 和 稍后运行 的机制。
此包解决了当前 PHP 实现的哪些问题? 易用性。 Amp 和 ReactPHP 都提供了异步结果。但两者都需要用户/开发者进行更多设置才能使简单的代码行运行。还有 Swoole 和 Hhvm,但它们都不是标准的 PHP 安装。
当使用 Amp 或 ReactPHP 以及基于它们的某些包时,你必须不仅要管理你提供的 Callbacks
,还要管理返回的 Promise
对象和 Event Loop
对象。这些库模仿旧的 Javascript,而现在的 Javascript
正在向简单的 async/await
语法转变。这引发了一个问题。
易用性意味着什么? 我们可以先回顾一下其他编程语言的实现。并且你已经有了一个正在运行的应用程序,你希望各个部分运行得更加响应,这相当于要做更多。
Go 有其 Goroutine 关键字 go
,然后是你的函数和语句。
以下链接详细介绍了 Python、Ruby、Rust、Nim、C#、Elixir、Java 和 C++ 的 async/await
关键字与函数和语句的结合。
使用它们时,它们返回你真正想要的结果,无需干预任何对象,无需回调,在调用后,一切到此为止的代码继续运行。
关于每个语言的,它们的历史、实际行为、底层概念都是相同的,调用结构简单。它们都以各种方式引用了 yield
或 迭代器
的使用,这正是此包所依赖的,并使以下情况成为可能。
要使代码异步,你只需在代码中放入yield
,并在调用代码前加上yield
前缀,以获取你想要的实际results
。
使用此包,你可以通过使用yield
来获得一个async/await
的PHP
版本。
有一些辅助函数可以将一切结合起来。主要的是away()
,它类似于Python的create_task(),其行为类似于Google的go()关键字,此处作为别名函数go()
包含在内。
使用它将立即返回一个数字,可以与另一个类似Python的函数一起使用,该函数也类似于Google的WaitGroup。这将等待并返回运行在后台的代码的远端/分离的结果。
此包遵循一种新的范例行为编程,该编程使用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异步实现重新创建此功能,它们需要首先重写才能接近。
这里的协程是基于generators
特别定制的函数,使用yield
和yield from
。当使用时,它们控制上下文,即捕获/释放应用程序的执行流。
当在代码块中放置yield
时,它向调用函数指示,将返回一个对象,代码不会立即执行。
此包代表调用函数,即调度器
,类似于事件循环
。协程需要被调度以运行,一旦调度,协程就被包装在一个Task
中,而Task
是一种类型的Promise
。
Task
是一个表示一些要完成的工作的对象,可能在结束时有一个结果。这些任务被注册在负责运行它们的调度器上。
由于PHP(没有扩展的情况下)的单线程本质,我们无法将Task
视为进行单次long-running
计算——这将block
单线程,直到任务完成。
相反,tasks
必须在可能的情况下执行小块/迭代(称为'ticks')的工作,并在适当的时候将控制权交回调度器。这被称为
cooperative multi-tasking
(之所以称为合作多任务,是因为任务必须通过自愿释放控制权来合作)。
调度器负责“tick”已调度的任务,每个已调度的任务都会被反复“tick”直到完成。调度器实现如何以允许所有已调度的任务运行的方式进行,取决于调度器实现。
一个 任务
可以通过以下三种方式之一完成:
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
点,这些点定义了当有其他任务等待时可能发生 上下文切换 的位置,如果没有其他任务等待则不会发生。这也可以被视为 断点/陷阱,就像在使用调试器时,当触发时,调试器会介入,您可以查看状态并逐步执行剩余的代码。
一个 上下文切换 代表 调度器 将控制流从一个 协程 切换到下一个。
在这里,协程 被定义为包含
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
支持,或真正转换/重写某些包。 - 添加
Database
支持,基于我对 ezsql 的维护。 - 添加更多来自其他语言的标准化示例,并转换。
- 更新文档,以参考 Python、Go 或任何其他语言中功能类似的部分。
致谢/参考
Nikita Popov 使用协程在 PHP 中实现合作多任务。该软件包 分支 Ditaio,重构/重写。
并行类是对spatie/async的重构/重写。该并行类依赖于symplely/spawn,用于子进程的管理/执行,它使用libuv的uv_spawn
来启动进程。Spawn包依赖于opis/closure,用于克服PHP序列化的限制,并且当没有安装PHP-UV扩展时,使用symfony/process作为备选方案来使用proc_open
启动进程。
贡献
鼓励并欢迎贡献;我总是很高兴在Github上收到反馈或pull requests :) 为错误和新特性创建Github Issues,并对您感兴趣的问题进行评论。
许可证
MIT许可证(MIT)。请参阅许可证文件以获取更多信息。