协程之质数与Channel

协程之质数与Channel

今天要说的这个问题是协程中对于Channel的利用,这也是官方文档中使用的一种算法。
官方文档:质数与Channel

代码很简单,只有2个方法加上一个main函数,这里搬运一下:

fun main() = runBlocking {
    var cur = numbersFrom(2)
    repeat(10) {
        val prime = cur.receive()
        println(prime)
        cur = filter(cur, prime)
    }
    coroutineContext.cancelChildren() // cancel all children to let main finish
}

fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
    var x = start
    while (true) send(x++)
}

fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
    for (x in numbers) if (x % prime != 0) send(x)
}

Channel就是用来在不同的协程之间传递数据用的,这里不做讨论,主要探讨一下这个质数的算法:

这里通过

repeat(10) {
        val prime = cur.receive()
        println(prime)
        cur = filter(cur, prime)
    }

启动了10个协程
一个numbersFrom和九个filter

numberFrom方法为最开始的生产者。这个方法就是用来产生从2开始,递增的无限数字。

filter方法这里是将上一次求得的质数,从产生的数字队列里进行一个整除,如果整数的结果不为0,则这个数为质数。

这里,重点的地方就是这个上一次,我们要进行这样一个思考,我们从这个方法中

for (x in numbers)

得到的数字究竟是什么,为什么用这个值除以上一次得到的质数,如果不整除,这个数字就是质数呢。

为了我们研究结果更清晰,这里我加一个log,这个log可以打印出协程的具体信息。这里我们修改一下filter。

fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
    for (x in numbers) {
        log("       $x")
        if (x % prime != 0) {
            send(x)
        }
    }
}

然后 我们将repeat次数缩小到1次,这时候运行后再打印一下。结果如下:

2

Process finished with exit code 0

这里我们看到,由于在main函数中调用了coroutineContext.cancelChildren(),结束了所有的子协程,所以这里并没有打印任何跟filter有关的log。这时候我们得到了一个质数2.在repeat只有一次的时候是很清晰的,就是从numbersFrom方法中生产出一个数字,我们拿来使用。

这时候,我们将repeat次数改为2次,再运行一次,查看打印结果:

2
[main @coroutine#3]        3
3
[main @coroutine#3]        4

Process finished with exit code 0

结果显示,在filter方法中,我们接收到了3和4这两个数字。那么,这两个数字是怎么来的呢?
我们在回过头看repeat这里:

repeat(2) {
        val prime = cur.receive()
        println(prime)
        cur = filter(cur, prime)
    }

可以看到第一次,我们将获得的2这个质数,带入了filter这个函数,然后将numbersFrom这个生产者也带入了filter这个函数。也就是说,这里的filter函数for循环中,从生产者中拿数字,就是从numbersFrom这个方法中拿取的。
而numbersFrom这个函数,有没有停止呢?没有!它还是在一直的生产数字,每次递增1,由于第一次我调用了receive这个方法,从中拿取了一个2,所以下次它在生产数字的时候就是从3开始了。
这里,非常的重点,就是要记住numbersFrom这个函数是在一直一直生产数字的,它是没有停止的,这个也是我们数字集的一个根本来源。
那么,看到这里是不是可以明白filter的3和4是从哪里来的了呢?对,就是从numbersFrom这个生产者中获取的!
这里从生产者中取得了一个3,然后跟上一次拿到的质数也就是2,做除法,如果不能整除,那么它就是质数,也就是调用send方法,发送出去。
然后再来看main函数的这里:

val prime = cur.receive()

除了第一次是从numbersFrom中拿取了一个2,之后的每一次都是从filter中获取数字。
所以这里获取了filter中发来的3这个数字。然后停掉了整个协程。所以这里当repeat是2次的时候,我们拿到了2,3 这两个质数。

好,这里我们理清了filter方法从numbersFrom拿数字的情况,那么,重点来了!!!
当repeat设置为3的时候,我们再运行一下程序,获得的结果如下:

2
[main @coroutine#3]        3
3
[main @coroutine#3]        4
[main @coroutine#3]        5
[main @coroutine#4]        5
[main @coroutine#3]        6
5

Process finished with exit code 0

是不是觉得一头雾水?3、4、5、5、6?这是什么数列?接下来就对这里重点分析,如果这里明白了,那么拿10个质数,100个质数,拿多少个质数就都明白了。

我们当repeat为2的时候,filter还是从numbersFrom拿的数据,但是,当repeat为3的时候,filter就改为了从filter中拿数据。

那么,当repeat为3的时候,我们来看这个结果,这里,我们要看同协程的数字,不要一起看,不然一定会绕晕。

[main @coroutine#3]        3
[main @coroutine#3]        4
[main @coroutine#3]        5
[main @coroutine#3]        6

要知道,我们这时候已经启动了3个子协程,numbersFrom和2个filter。
也就是说第一个子filter,也就是上面结果中的coroutine3子协程,它还在一直进行整除的判断。
也就是说从numbersFrom还在不断生产数字5 和 6,这时候我还是要在第一个filter子协程中进行和第一个质数2来做整除判断。

第一个filter子协程拿到5,做了一个整除判断,这时候满足了这个数字不能被第一个质数也就是2整除这个条件,所以第一个filter子协程将5这个数字send出去了。
这里要注意,谁来接收的5这个数字?不是main函数!!!

而是第二个filter的子协程,那么第二个filter的子协程又做了什么呢。拿到了第一个子协程发来的5这个数字,跟第二个质数3做判断,如果不能整除,那么就将5这个数字发送出去。
至此,我们main函数的主协程中receive方法才能接收到5这个数字。

这就是这个教程的设计思路巧妙之所在啊。

再屡一下:
第一个子协程numbersFrom生产了5这个数字
然后第二个子协程也就是第一个filter子协程,拿到5这个数字与2做整除比较,不能整除则发送给第三个子协程,也就是第二个filter子协程。
最后第三个子协程也就是第二个filter子协程,拿到5这个数字再与3做整除比较,如果不能整除则发送给main协程的receive方法中。

这样每产生一个数字,都要经过跟所有当前已知的质数做整除比较,层层递进给下一个子协程做整除比较,如果到了最外层的子协程还是不能整除,那么这个数字就是质数,这时候就被我们的main函数的receive方法接收到,并打印出来。

所以当repeat次数为10的时候呢,同理,通过这种层层递进的整除比较就可以拿到10个质数了。

当然了,这里只是讨论一下官方文档的这个设计思路,真正的获取质数的方法,肯定是不会建这么多协程去取的。
这里官方文档也做了这个说明

Anyway, this is an extremely impractical way to find prime numbers. In practice, pipelines do involve some other suspending invocations (like asynchronous calls to remote services) and these pipelines cannot be built using sequence/iterator, because they do not allow arbitrary suspension, unlike produce, which is fully asynchronous.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值