生成器與協程

操作系統會爲每個函數分配一個棧幀,但是對於python中生成器所在函數,其棧幀是分配在堆上面的,所以其函數運行狀態能夠一值保存。此即生成器實現原理。

做個實驗,打印生成器函數地址和普通函數地址

def yieldFunc():
    for i in range(5):
        yield i

def normalFunc1():
    for i in range(4, 5):
        return i

def normalFunc2():
    for i in range(3):
        return i

a = yieldFunc()
b = yieldFunc()
c = yieldFunc()
d = normalFunc1()
e = normalFunc2()
print(f"a in yieldFunc: {hex(id(a))}")
print(f"b in yieldFunc: {hex(id(b))}")
print(f"c in yieldFunc: {hex(id(c))}")
print(f"d in normalFunc: {hex(id(d))}")
print(f"e in normalFunc: {hex(id(e))}")

Output

a in yieldFunc: 0x159f65b5b10
b in yieldFunc: 0x159f65b5c78
c in yieldFunc: 0x159f65b5cf0
d in normalFunc: 0x7ffd891f62f0
e in normalFunc: 0x7ffd891f6270

上述實驗可以論證,在yield函數中的變量是存在堆中的,普通函數的變量是存在棧中的(我的系統中堆向上增長,棧向下增長)

協程是一種基於生成器的抽象模式。協程不是並行的,協程是單線程的!
協程是一種生產者消費者模型的設計模式。

請看下面代碼:

class Producer:
    def produce(self):
        while True:
            data = generateData()
            sendToConsumer(data)

class Consumer:
    def consume(self):
        while True:
            data = receiveFromProducer()
            onReceivedData(data)

Producer一直生產數據,然後發送給Consumer。Consumer一直讀取Producer生產的數據,然後做相應處理。這是兩個並行的過程,但是他們實際又是相互依賴的,Consumer每次只能處理一個Producer生產的數據。當Consumer處理數據時,Producer被generateData() 或者sendToConsumer() block,當Producer生產數據時,Consumer又會一直被receiveFromProducer() block。特別是有IO操作時對系統的性能影響很大。
所以更好的設計模式應是由Consumer來驅動。參見如下代碼:

def sendToConsumer(data):
    originalSendToConsumer(data)
    yield data

class Producer:
    def produce(self):
        while True:
            data = generateData()
            sendToConsumer()

def receiveFromProducer(producer):
    producer.produce()
    data = originalReceiveFromProducer()
    yield data

class Consumer:
    def __init__(self, producer):
        self.producer = producer

    def consume(self):
        while True:
            data = receiveFromProducer(self.producer)
            onReceivedData(data)

producer = Producer()
consumer = Consumer(producer)
while True:
    consumer.consume()

只有當Consumer需要數據時,Producer才生產數據。
更加典型的例子就是管道: produce() | consume()。當consume()需要數據寫入時,管道resume(produce())。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章