新聞中心
Python 的 generator 和 Go 的 goroutine 都是常用的技法。沒看到有人分析其間關(guān)系,所以在此記錄一下。

成都創(chuàng)新互聯(lián)主營喀左網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,app軟件開發(fā),喀左h5成都微信小程序搭建,喀左網(wǎng)站營銷推廣歡迎喀左等地區(qū)企業(yè)咨詢
這兩個概念都是為了 producer-consumer 模式的編程方便發(fā)明的。Python 的 generator 和 iterator 以及 iterable objects 一脈相承。Go 出現(xiàn)比 Python 晚,解決同樣的編程便捷性問題,用 channel 和 goroutine 兩個概念。
Go 的做法
Go 的做法比較容易理解,因為和教材里的概念一致:producer 和 consumer 各自是一個 goroutine,而一個 goroutine 是一種 green thread —— 自己放棄執(zhí)行,讓其他 gorotine 有機會占用 CPU,而不依賴一個 preemption 機制(比如 OS kernel)來強制休眠當(dāng)前 thread 以騰出 CPU 給其他 thread。
producer 把數(shù)據(jù)寫入一個 channel,consumer 從這個 channel 里讀。一個 channel 就是一個 blocking queue,可以有一個 buffer。讀可以通過 loop 語法。比如
- package main
- func producer(n int) chan int {
- ch := make(chan int)
- go func() { // This goroutine is the producer
- for i := 0; i < n; i++ {
- ch <- i
- }
- close(ch)
- }()
- return ch
- }
- func main() { // the main goroutine is the consumer
- for i := range producer(5) {
- println(i)
- }
- }
請注意,上述寫法讓一個 Go 函數(shù)創(chuàng)建和返回一個 channel,同時這個 Go 函數(shù)啟動一個“發(fā)射后不管”的 producer goroutine —— 這是標準 Go 做法,不太符合 C/C++ 的習(xí)俗 —— (1)創(chuàng)建 channel(2)啟動 producer 和 consumer threads。這是因為 C/C++ 不支持 high-order functions,或者叫 functionals。具體請參見我的這個回答 什么是函數(shù)式編程思維? 這個 Go pattern 和 Python 習(xí)俗一致,因為這倆都是 functional programming languages。
Python 的做法
上述 Go 的 producer 非常接近 Python 的 generator 的寫法 —— 兩點區(qū)別,都是 Python 解釋器代勞的結(jié)果:
- Python 用戶不需要創(chuàng)建和關(guān)閉 channel 了。
- ch <- i 這一行可以用 yield i 來代替。
對應(yīng)的 Python generator 如下
- from typing import Iterator
- def producer(n: int) -> Iterator[int]:
- for i in range(n):
- yield i
- for i in producer(5):
- print(i)
比較
Python 的 producer 不是一個函數(shù),因為里面沒有 return,而是一個 generator,因為里面有 yield。一個函數(shù)返回一個值。而一個 generator 返回一個 iterator。
Go 的 producer 是一個函數(shù),返回一個 channel。Go 里沒有 generator 這樣的“新概念”。
上面 Python generator 里的代碼和 Go producer 里啟動的 goroutine 的代碼幾乎完全一樣,只是把 ch <- i 換成了 yield i。
那么 Python generator 返回的 iterator 到底是個啥呢?其實就是那個 Go channel,或者叫 blocking queue 的。從這個角度看,Python generator 又是一個函數(shù)了,返回一個 blocking queue。
Python 里最常用的 generator 莫過于 range —— 上例中也出現(xiàn)了。所以上例中,其實調(diào)用 range 的時候,已經(jīng)創(chuàng)建了一個 Python thread 往 range 返回的 blocking queue 里寫數(shù)字。而 producer 只是從這個 queue 里取出數(shù)字,再 yield 到 producer 創(chuàng)建的第二個 queue 里,讓 for i in producer(5) 這一行(由 main thread 執(zhí)行)去讀。
這樣一串三個 Python threads,通過兩個 queues 連成一串,就是 Rob Pike 在著名幻燈片 https://talks.golang.org/2012/concurrency.slide#1 里展示 Go concurrency pattern 里的 pipeline:
不過這里有一個區(qū)別,goroutines 是可以并行執(zhí)行的,如果我們電腦里有多個 CPU cores。不過,Python threads 雖然就是 OS thread 卻受制于 Python 的 GIL,所以任何時候只有一個 Python 在執(zhí)行中,即使我們有很多 CPU cores。請看https://www.zhihu.com/pin/1343421894465474560
Occam's Razor
我們設(shè)計系統(tǒng)的時候經(jīng)常需要遵循一個哲學(xué)原則 Occam's Razor —— 能達到目的的各種手段里我們選擇最簡單的那個。這也是本專欄名字的由來。在漢語里,這個原則(philosophical principle)叫“刪繁就簡三秋樹”。如果做不到,必然積累還不完的技術(shù)債,以至于不可能“領(lǐng)異標新二月花”。
對比上面 Go 和 Python 兩個例子,顯然 Python 例子的代碼更簡單。那么是不是就說明 Python 語言的設(shè)計比 Go 更加符合 Occam's Razor 的原則了呢?
恐怕并不盡然。雖然 Python 代碼簡短,但是需要用戶理解更多概念(generator,iterator,以及它們和 functions 以及 queues 的潛在關(guān)系)—— 這也是一種開銷。
這里只是提醒大家關(guān)注保持設(shè)計的簡潔。不在于挖坑比較 Python 和 Go 語言。如果回復(fù)有涉及這樣比較的,恕刪。:-)
本文名稱:Python的Generator和Go的ConcurrencyPattern
URL分享:http://m.5511xx.com/article/dpodhgi.html


咨詢
建站咨詢
