Swoole 协程Channel实现原理

为了给我们的扩展实现Channel做准备,这里,很有必要简单分析一下Swoole协程的Channel实现原理。核心点如下:

1
2
3
4
5
6
1、什么情况下可以pop
2、当channel不可以pop的时候,应该如何处理消费者协程
3、什么情况下可以push
4、当channel不可以push的时候,应该如何处理生产者协程
5、当channel可以pop的时候,应该如何通知消费者协程
6、当channel可以push的时候,应该如何通知生产者协程

如果解决了这些问题,就可以去实现Channel了。

什么情况下可以pop

这个问题很简单,当channel里面有数据的时候。我们来看看Swoole对应的源码,在方法Channel::pop(double timeout)里面:

1
2
void *data = data_queue.front();
data_queue.pop();

消费者协程和生产者协程

我们定义一个协程是消费者协程还是生产者协程,取决于这个协程正在执行哪种操作。如果这个协程此时正在执行pop操作,那么这个协程此时就是消费者协程;如果这个协程此时正在执行push操作,那么这个协程此时就是生产者协程。也就是说,协程是生产者还是消费者不是死的,是会随着协程的操作动态变化的。

当channel不可以pop的时候,应该如何处理消费者协程

channel不可以pop的时候,我们应该挂起这个消费者协程。我们来看看Swoole对应的代码:

1
2
3
4
5
6
if (is_empty() || !consumer_queue.empty())
{
// 省略其他代码
yield(CONSUMER);
// 省略其他代码
}

我们可以看到,当Channel为空的时候,消费者协程是不可以进行pop操作的,此时被yield出去了。

什么情况下可以push

这个问题很简单,当channel容器没有满的时候。我们来看看Swoole对应的源码,在方法Channel::push(void *data, double timeout)里面:

1
2
data_queue.push(data);
swTraceLog(SW_TRACE_CHANNEL, "push data to channel, count=%ld", length());

当channel不可以push的时候,应该如何处理生产者协程

channel不可以push的时候,我们应该挂起这个生产者协程。我们来看看Swoole对应的代码:

1
2
3
4
5
6
if (is_full() || !producer_queue.empty())
{
// 省略其他的代码
yield(PRODUCER);
// 省略其他的代码
}

我们可以看到,当Channel满了的时候,生产者协程是不可以进行push操作的,此时被yield出去了。

当channel可以pop的时候,应该如何通知消费者协程

首先,我们这里需要明白,是谁在通知消费者协程。是Swoole的那套事件循环吗?不是的。

通知消费这协程的是生产者协程。我们来看看pop的逆操作push的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* push data
*/
data_queue.push(data);
swTraceLog(SW_TRACE_CHANNEL, "push data to channel, count=%ld", length());
/**
* notify consumer
*/
if (!consumer_queue.empty())
{
Coroutine *co = pop_coroutine(CONSUMER);
co->resume();
}

可以看到,当生产者协程push完数据的时候,此时channel必定是有数据的。然后,如果有消费者协程在等待channel的话,那么就唤醒第一个等待的那个消费者协程。(所以规则是:谁先等待channel,谁就先被唤醒)

当channel可以push的时候,应该如何通知生产者协程

这个的原理和上一个问题的答案类似,不重复说了。一句话:消费者通知的生产者协程