三十四、Swoole 基础学习笔记 - Swoole Channel 协程间通信

作者: 温新

分类: 【Swoole 系列】

阅读: 1100

时间: 2023-03-13 12:22:43

hi,我是温新,一名PHPer

文章基于 Swoole 5.0.1 版本编写。

学习目标:了解协程间通信

说明:本篇文章结合官方文档编写及参考网络资料编写,虽非全部原创,但也是结合了自己的理解,若转载请附带本文 URL,编写不易,持续编写更不易,谢谢!

通道,用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。

什么是 Channel

Channel 可以理解为消息队列,只不过是协程间的消息队列,多个协程通过 pushpop 操作队列中的生产消息和消费消息,用来发送或者接收数据进行协程之间的通讯。需要注意的是 Channel 是没法跨进程的,只能一个 Swoole 进程里的协程间通讯,最典型的应用是连接池和并发调用。

实现原理

  • 通道与 PHPArray 类似,仅占用内存,没有其他额外的资源申请,所有操作均为内存操作,无 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 版本下 zval16 字节,如 $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

我是温新,本篇文章结束。

请登录后再评论