Python 的 generator 和 Go 的 goroutine 都是常用的技法。没看到有人分析其间关系,所以在此记录一下。

这两个概念都是为了 producer-consumer 模式的编程方便发明的。Python 的 generator 和 iterator 以及 iterable objects 一脉相承$ 2 d 4 @ `。Go 出现比 P] 4 ] iythk n ~ } Y [ !on 晚,解决同样的编程便捷性问题,用 channel 和 goroutine 两个概念。; \ X T | R , n

Go 的做法

Go 的做法比较容易理解,因为和教材里的概念一致:producer 和 consumer 各自是一个 goroutine,而一个 goroutine 是一种 green th& \ @read —— 自己放弃执行,让其他 gorotine 有机会占用Q d J X 3 – & CPU,而不依赖一个 preemptI J 5 X cion 机制(比如 OG H ( 5 # n * x /S kernel)来强制休眠$ @ I Z ^当前 thread 以腾出 CPU 给其他 thread。

producer 把数据写入一个 channel,cJ + [onsumer 从这个 channel 里读。一个 channel 就是一个 blocking queue,可以有一个 buffer。读可以通过 loop 语` L . Z +法。比如

  1. packagemain
  2. funcproducer(nint)chanint{
  3. ch:=make(chanint)
  4. gofunc(){//Thisgoroutinei\ 4 C ^stheproducer
  5. fork 7 W 4i:=0;i<n;i++{
  6. ch<-i
  7. }
  8. close(ch)
  9. }()? , O ( { } G Z )
  10. returnch
  11. }
  12. funcmain( Z E @ ! 1(){//themaingoroutineistheconsumer
  13. fori:=rangeproducer(5){
  14. println(i)
  15. }
  16. }

请注意,上述写法让一个 Go 函数创建和返回一个 channe– s rl,同时这个 Go 函数启动一个“发射后不管”k = 8的 producer goroutine —— 这是标准 Go 做法,不太符合 C/C++ 的习俗 —— (1)创建 channel(2)启动 producer 和 ct % \ + 4 c : –onsumeS p h * \ Zr threads。这是因为 C/C++ 不支持 high-order functions,或者叫 funcg ~ ^ L w e 0 ;tionaw | Els。具z F m E %体请参见我的这个回答 什么是函数式编程思维? 这个 Go pattern 和 Python 习俗一致,因为这俩都是 functionaH r ? C ` Y $ q 9l programming languages。

Python 的做法

上述 Go 的 producer 非常接近 Python 的 g_ 3 | 1 z K w +enerator 的写~ F L ; % Z 6 T法 —— 两点区别,都是 Python 解释器代劳的结果:

  • Python 用户不需要创建和关闭 channel 了。
  • ch <- i 这一行可以用 yield i 来代替。

对应的 Python generator 如下

  1. fromtd s ` ~ / & B O typingimportIterator
  2. defproducer(n:int)->Iterator[int]:
  3. foriinrange(n):
  4. yieldi
  5. foriinproducer(5):
  6. print(i)

比较

Python 的 producer 不是一个函数,因为D m 9 v / Z G b里面没有 return,而是一个 generator,因为里面有Q z % yield。一个函数返回( g H $一个值。而一个 generator 返回一个 iterator。

Go 的 producer 是一个函{ . u F V 0数,返回一个 channel。Go 里没有 generator 这样的“新概念”。

上面 Python generator 里的代码和 Go producer 里启动的 goroutine 的代码几乎完~ p r u a 8全一b z /样,只是把 ch <- i 换成了 yield i。

那么 Python generator 返回的 iterator 到底是个啥呢?其实就是那个 Go channel,或者叫 blocking queue 的。从这个角度看,Python generator 又是一个函数了,返回一个 block2 w C n / iing queue。

Python 里最常用的 gv 9 y u A i 3 \ {enerator 莫过于 range —— 上例中也出现了。所以上例中,其实调用 range 的时候,已经创建了一个 Python thread 往 range 返回的 blocking que8 d $ ; (ue 里写数字。而 producer 只是从这个 queue 里取出数字,再 yield 到 producer 创建的第二个 queue 里,让 for i in producer(5) 这一行(由 main thread 执行)去读。

这样一串三个 Python threads,通过两个 queues 连成一串,就r E z b M是 Rob Pike 在著名L B S S ^ + 1 p U幻灯片 https://talks.golang.org/2012/w 2 Z m x | b ~ qconcO N % * u eurrency.slide#1 里展示 Go concurrency pattern 里的 pipeline:

不过这里有一个区别,goroutines 是可以并行执行的,如果我们电脑里有多个 CPU cg k ` y ? \ores。不过,Python threads 虽然就是 OS thread 却受制于 Python 的 Go # e { ` ;IL,所以任何时候只有一个 Python 在执行中,即使我们有很多 CPU cores。请看https://www.zhihu.com; # T Y/pin/1B L C y3% E N { u # [ h {43421894465474560

O{ % ) M ] Mccam’s Razor

我们设计系统的时候经常需要遵循一个哲学原则 Occam’s Razor —— 能达到目的的各种手段里我们选择最简单的那个。这1 q l 5 ? 1 5 q也是本专栏名字~ z n ] :的由来。在汉语里,这个原则(philosophical princiE ` p ; ! u ,ple)叫“删繁就简三秋树”。如果做不到,必然积累还不完的技术债,以至于不可能“领异标新二月花”。

对比上面 Go 和 Python 两个例子,显然 Python 例子的代码更简单。那么是不是就说明 Python 语言q % i E I z的设计比 Go 更加符合 Occam’s Razor 的原则了呢?

恐怕并不尽然。虽然 PythH O / d r ; :on 代码简短,q u Q \但是需要用户理解更多概念(generator,iterator,以及它们和 functions 以及 queues 的潜在关系)—— 这也是一种开销。

这里只是提醒大家关注保持设计的简洁。不在于挖坑比较 Python 和 Go 语言。如E & / L t `果回复有涉及这样比较的,恕删。:-)

【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0