三十四、Swoole 基础学习笔记 - Swoole Channel 协程间通信
hi,我是温新,一名PHPer
文章基于 Swoole 5.0.1 版本编写。
学习目标:了解协程间通信
说明:本篇文章结合官方文档编写及参考网络资料编写,虽非全部原创,但也是结合了自己的理解,若转载请附带本文 URL,编写不易,持续编写更不易,谢谢!
通道,用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。
什么是 Channel
Channel
可以理解为消息队列,只不过是协程间的消息队列,多个协程通过 push
和 pop
操作队列中的生产消息和消费消息,用来发送或者接收数据进行协程之间的通讯。需要注意的是 Channel
是没法跨进程的,只能一个 Swoole
进程里的协程间通讯,最典型的应用是连接池和并发调用。
实现原理
- 通道与
PHP
的Array
类似,仅占用内存,没有其他额外的资源申请,所有操作均为内存操作,无IO
消耗 - 底层使用
PHP
引用计数实现,无内存拷贝。即使是传递巨大字符串或数组也不会产生额外性能消耗 -
channel
基于引用计数实现,是零拷贝的
通信案例
<span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine\run</span>(<span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">function</span> () {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(98, 151, 85) !important">// 实例化通道</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$channel</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=</span> <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">new</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine\Channel</span>(<span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">1</span>);</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box"></span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(98, 151, 85) !important">// 这个协程负责向通道中写入数据</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine</span>::<span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">create</span>(<span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">function</span>() <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">use</span> (<span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$channel</span>) {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">for</span> (<span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$i</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">0</span>; <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$i</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important"><</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">10</span>; <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$i</span><span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">++</span>) {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine</span>::<span style="box-sizing: border-box;color: rgb(232, 191, 106) !important">sleep</span>(<span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">1</span>);</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(98, 151, 85) !important">// 通道中写入数据</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$channel</span><span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">-></span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">push</span>([</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(152, 195, 121) !important">'rand'</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=></span> <span style="box-sizing: border-box;color: rgb(232, 191, 106) !important">rand</span>(<span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">1000</span>, <span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">9999</span>),</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(152, 195, 121) !important">'index'</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=></span> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$i</span>,</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> ]);</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">echo</span> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$i</span> . <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">PHP_EOL</span>;</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> }</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> });</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box"></span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> </span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> </span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(98, 151, 85) !important">// 这个协程负责从通道中读取数据</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine</span>::<span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">create</span>(<span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">function</span> () <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">use</span> (<span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$channel</span>) {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">while</span> (<span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">true</span>) {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$data</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=</span> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$channel</span><span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">-></span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">pop</span>(<span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">2</span>);</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">if</span> (<span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$data</span>) {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> </span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(232, 191, 106) !important">print_r</span>(<span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$data</span>);</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> } <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">else</span> {</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(232, 191, 106) !important">assert</span>(<span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$channel</span><span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">-></span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">errCode</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">===</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">SWOOLE_CHANNEL_TIMEOUT</span>);</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> <span style="box-sizing: border-box;color: rgb(198, 120, 221) !important">break</span>;</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> }</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> }</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> });</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px">});</span>
API 学习
__construct
含义:通道构造方法。
语法:
<span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine\Channel</span>::<span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">__construct</span>(<span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">int</span> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$capacity</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">1</span>)</span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box"></span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(98, 151, 85) !important"># 参数</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$capacity</span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">:设置容量</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">【必须为大于或等于</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">1</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">的整数】</span></span>
底层使用 PHP 引用计数来保存变量,缓存区只需要占用
$capacity * sizeof(zval)
字节的内存,PHP7
版本下zval
为16
字节,如$capacity = 1024
时,Channel
最大将占用16K
内存
push
含义:向通道中写入数据。
语法:
<span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">Swoole\Coroutine\Channel</span><span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">-></span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">push</span>(<span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">mixed</span> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$data</span>, <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">float</span> <span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$timeout</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">=</span> <span style="box-sizing: border-box;color: rgb(86, 182, 194) !important">-</span><span style="box-sizing: border-box;color: rgb(209, 154, 102) !important">1</span>): <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">bool</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"> </span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(98, 151, 85) !important"># 参数</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$data</span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">:push</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">数据</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">【可以是任意类型的</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">PHP</span> <span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">变量,包括匿名函数和资源】;</span></span><br></br><span style="box-sizing: border-box;color: rgb(171, 178, 191);padding-right: 0.1px"><span style="box-sizing: border-box;color: rgb(224, 108, 117) !important">$timeout</span><span style="box-sizing: border-box;color: rgb(18, 170, 228) !important">:超时时间。</span></span>
在通道已满的情况下,
push
会挂起当前协程,在约定的时间内,如果没有任何消费者消费数据,将发生超时,底层会恢复当前协程,push
调用立即返回false
,写入失败
扩展:
通道已满:
1、自动
yield
当前协程,其他消费者协程pop
消费数据后,通道可写,将重新resume
当前协程;2、多个生产者协程同时
push
时,底层自动进行排队,底层会按照顺序逐个resume
这些生产者协程。通道为空:
1、自动唤醒其中一个消费者协程;
2、多个消费者协程同时
pop
时,底层自动进行排队,按照顺序逐个resume
这些消费者协程。
pop
含义:从通道中读取数据。
语法:Swoole\Coroutine\Channel->pop(float $timeout = -1): mixed
扩展
通道已满
1、
pop
消费数据后,将自动唤醒其中一个生产者协程,让其写入新数据;2、多个生产者协程同时
push
时,底层自动进行排队,按照顺序逐个resume
这些生产者协程。通道为空
1、自动
yield
当前协程,其他生产者协程push
生产数据后,通道可读,将重新resume
当前协程;2、多个消费者协程同时
pop
时,底层自动进行排队,底层会按照顺序逐个resume
这些消费者协程。
stats
含义:获取通道的状态。
语法:Swoole\Coroutine\Channel->stats(): array
返回值:
- 返回一个数组,缓冲通道将包括
4
项信息,无缓冲通道返回2
项信息 -
consumer_num
消费者数量,表示当前通道为空,有N
个协程正在等待其他协程调用push
方法生产数据 -
producer_num
生产者数量,表示当前通道已满,有N
个协程正在等待其他协程调用pop
方法消费数据 -
queue_num
通道中的元素数量
close
含义:关闭通道。并唤醒所有等待读写的协程。
语法:Swoole\Coroutine\Channel->close(): bool
length
含义:获取通道中的元素数量。
语法:Swoole\Coroutine\Channel->length(): int
isEmpty
含义:判断当前通道是否为空。
语法:Swoole\Coroutine\Channel->isEmpty(): bool
isFUll
含义:判断当前通道是否已满。
语法:Swoole\Coroutine\Channel->isFull(): bool
我是温新,本篇文章结束。