From b8a706e0cd80dc6a0ab1bb23462bad9f6312abf0 Mon Sep 17 00:00:00 2001 From: farwish Date: Sat, 31 Aug 2019 03:57:55 +0800 Subject: [PATCH] coroutine --- "5.Swoole\345\215\217\347\250\213/README.md" | 176 +++++++++++++++++- .../order_co.php" | 49 +++++ .../runtime_co.php" | 22 +++ .../waitgroup_co.php" | 112 +++++++++++ 4 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 "5.Swoole\345\215\217\347\250\213/order_co.php" create mode 100644 "5.Swoole\345\215\217\347\250\213/runtime_co.php" create mode 100644 "5.Swoole\345\215\217\347\250\213/waitgroup_co.php" diff --git "a/5.Swoole\345\215\217\347\250\213/README.md" "b/5.Swoole\345\215\217\347\250\213/README.md" index 59c2b9a..786b7b4 100644 --- "a/5.Swoole\345\215\217\347\250\213/README.md" +++ "b/5.Swoole\345\215\217\347\250\213/README.md" @@ -1,6 +1,6 @@ # Swoole 协程 -## CSP 编程方式 +## CSP 编程方式 [co.php] CSP 编程 @@ -25,4 +25,178 @@ Swoole 协程特点 协程的切换是隐式发生的,所以协程切换前后不保证全局变量和静态变量的一致性(不安全)。 ``` +## 网络客户端一键协程 [runtime_co.php] + +Swoole\Runtime + +``` +Swoole-4.1.0 版本新增,在运行时动态将 PHP Stream 实现的 扩展、网络客户端代码协程化。 + +底层替换了 ZendVM、Stream 的函数指针,所有使用 Stream 进行 socket 操作均变成协程调度的异步 IO。 +``` + +开启方式 + +``` +Swoole\Runtime::enableCoroutine(bool $enable = true, int $flags = SWOOLE_HOOK_ALL); + +$enable 打开或关闭协程,$flags 选择要 Hook 的类型,仅在 $enable = true 时有效,默认全选。 + +@支持的选项 https://wiki.swoole.com/wiki/page/993.html +``` + +``` +Swoole\Runtime::enableCoroutine(int $flags = SWOOLE_HOOK_ALL); +# Swoole-4.3.2 + +Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]); +# Swoole-4.4 +``` + +可用场合 + +``` +redis 扩展 + +使用 mysqlnd 模式的 PDO、MySQLi 扩展 + +soap 扩展 + +stream_socket_client、stream_socket_server、stream_select ( 4.3.2以上 ) + +fsockopen + +file_get_contents、fopen、fread/fgets、fwrite/fputs、unlink、mk(rm)dir + +sleep、usleep +``` + +使用位置 + +``` +调用后当前进程全局有效,一般放在整个项目最开头,只在 Swoole 协程中会被切换为协程模式, + +在非协程中依然是同步阻塞的,不影响 PHP 原生环境使用。 + +( Swoole-4.4.0 中不再自动兼容协程内外环境,一旦开启,则一切阻塞操作必须在协程内调用 ) + +不建议放在 onRequest 等回调中,会多次调用造成不必要的开销。 +``` + +## 协程编程须知 + +自动创建协程的回调方法 + +``` +onWorkerStart +onConnect +onOpen +onReceive +redis_onReceive +onPacket +onRequest +onMessage +onPipeMessage +onFinish +onClose +tick/after 定时器 + +当 enable_coroutine 开启后,以上这些回调和功能会自动创建协程,其余情况可以使用 go() 或者 Coroutine::create() 创建。 +@doc https://wiki.swoole.com/wiki/page/949.html +``` + +与 Go 协程的区别 + +``` +Swoole4 的协程调度是单线程的,没有数据同步问题,协程间依次执行。 +Go 协程调度器是多线程的,同一时间可能会有多个协程同时执行。 + +Swoole 禁止协程间公用 Socket 资源,底层会报错,Go 协程允许同时操作。 + +Swoole4 的 defer 设计为在协程退出时一起执行,在多层函数中嵌套的 defer 任务按照 先进后出 的顺序执行。 +Go 的 defer 与函数绑定,函数退出时执行。 +``` + +协程异常处理 + +``` +在协程编程中可直接使用 try/catch 处理异常,但必须在协程内捕获,不能跨协程捕获异常。 + +Swoole-4.2.2 版本以上允许脚本(未创建HttpServer)在当前协程中 exit 退出。 +``` + +协程编程范式 + +``` +协程内部禁止使用全局变量。 + +协程使用 use 关键字引入外部变量时禁止使用引用(&)。 + +协程之间通讯必须使用 channel (IPC、Redis 等)。 + +多个协程公用一个连接、使用全局变量/类的静态变量保存上下文会出现错误。 +``` + +## 协程执行流程 [order_co.php] + +遵循原则 + +``` +协程没有 IO 等待的执行 PHP 代码,不会产生执行流程切换。 + +协程遇到 IO 等待立即将控制权切换,IO 完成后,重新将执行流切回切出点。 + +协程并发,依次执行,其余同上。 + +协程嵌套执行,流程由外向内逐层进入,直到发生 IO,然后切到外层协程, +注意,父协程不会等待子协程结束。 +``` + +## 并发调用 + +并发 shell_exec + +``` +在 PHP 程序中经常使用 shell_exec 执行外部命令,普通的 shell_exec 是阻塞的,导致进程阻塞, + +Swoole 协程环境中可以使用 Co::exec 并发执行多个命令。 +``` + +setDefer 机制 + +``` +绝大部分协程组件都支持了 setDefer 特性,可以将请求响应式的接口拆分成为两个步骤, + +使用此机制可以实现先发送数据,再并发收取响应结果。 + +@doc https://wiki.swoole.com/wiki/page/604.html +``` + +子协程与通道实现并发请求 + +``` +主协程内创建一个 chan; + +主协程内创建 2 个子协程分别进行 IO 请求,子协程使用 use 应用 chan; + +主协程循环调用 chan->pop,等待子协程完成任务,进入挂起状态。 + +并发的两个子协程,完成请求的 调用 chan->push 将数据推送给主协程。 + +子协程完成请求后退出,主协程从挂起状态中恢复,继续向下执行。 + +@doc https://wiki.swoole.com/wiki/page/947.html +``` + +## WaitGroup 功能 [waitgroup_co.php] + +WaitGroup 功能 + +``` +在 Swoole4 中可以使用 channel 实现协程间的通信、依赖管理、协程同步。 + +简单来说,WaitGroup 就是主协程等待所有子协程结束后才退出的功能。 + +@address https://github.com/swoole/swoole-src/blob/v4.4.4/library/core/Coroutine/WaitGroup.php +``` diff --git "a/5.Swoole\345\215\217\347\250\213/order_co.php" "b/5.Swoole\345\215\217\347\250\213/order_co.php" new file mode 100644 index 0000000..b3df597 --- /dev/null +++ "b/5.Swoole\345\215\217\347\250\213/order_co.php" @@ -0,0 +1,49 @@ + 2000, +]); + +go(function () { + + echo "main co start " . co::getcid() . PHP_EOL; + + go(function () { + echo "child co start " . co::getcid() . PHP_EOL; + + sleep(2); + + echo "child co end " . co::getcid() . PHP_EOL; + }); + + go(function () { + echo "child co start " . co::getcid() . PHP_EOL; + + sleep(1); + + echo "child co end " . co::getcid() . PHP_EOL; + }); + + echo "main co end " . co::getcid() . PHP_EOL; +}); + +echo "end" . PHP_EOL; + +/* +main co start 1 +child co start 2 +child co start 3 +main co end 1 +end +child co end 3 +child co end 2 + */ diff --git "a/5.Swoole\345\215\217\347\250\213/runtime_co.php" "b/5.Swoole\345\215\217\347\250\213/runtime_co.php" new file mode 100644 index 0000000..47ed338 --- /dev/null +++ "b/5.Swoole\345\215\217\347\250\213/runtime_co.php" @@ -0,0 +1,22 @@ + 2000, +]); + +for ($i = 0; $i < 1000; $i++) { + go(function () { + echo 'A'; + sleep(5); + echo 'B'; + }); +} diff --git "a/5.Swoole\345\215\217\347\250\213/waitgroup_co.php" "b/5.Swoole\345\215\217\347\250\213/waitgroup_co.php" new file mode 100644 index 0000000..27f2d07 --- /dev/null +++ "b/5.Swoole\345\215\217\347\250\213/waitgroup_co.php" @@ -0,0 +1,112 @@ +chan = new Channel(1); + } + public function add(int $delta = 1): void + { + if ($this->waiting) { + throw new BadMethodCallException('WaitGroup misuse: add called concurrently with wait'); + } + $count = $this->count + $delta; + if ($count < 0) { + throw new InvalidArgumentException('negative WaitGroup counter'); + } + $this->count = $count; + } + public function done(): void + { + $count = $this->count - 1; + if ($count < 0) { + throw new BadMethodCallException('negative WaitGroup counter'); + } + $this->count = $count; + if ($count === 0 && $this->waiting) { + $this->chan->push(true); + } + } + public function wait(float $timeout = -1): bool + { + if ($this->count > 0) { + $this->waiting = true; + $done = $this->chan->pop($timeout); + $this->waiting = false; + return $done; + } + return true; + } +} + +// 以上使用的是 swoole-4.4.4 library 的 WaitGroup.php + +\Swoole\Runtime::enableCoroutine(true); + +\Swoole\Coroutine::set([ + 'max_coroutine' => 2000, +]); + +go(function () { + $wg = new WaitGroup; + + echo "main co start " . \co::getcid() . PHP_EOL; + + $wg->add(); + go(function () use ($wg) { + echo "child co start " . \co::getcid() . PHP_EOL; + + sleep(2); + + echo "child co end " . \co::getcid() . PHP_EOL; + + $wg->done(); + }); + + $wg->add(); + go(function () use ($wg) { + echo "child co start " . \co::getcid() . PHP_EOL; + + sleep(1); + + echo "child co end " . \co::getcid() . PHP_EOL; + + $wg->done(); + }); + + $wg->wait(); + echo "main co end " . \co::getcid() . PHP_EOL; +}); + +/* 未使用 waitgroup +main co start 1 +child co start 2 +child co start 3 +main co end 1 +child co end 3 +child co end 2 + */ + +/* 使用 waitgroup +main co start 1 +child co start 2 +child co start 3 +child co end 3 +child co end 2 +main co end 1 + */