Python 为什么发电机功能不使用空闲时间来准备下一个产量?

Python 为什么发电机功能不使用空闲时间来准备下一个产量?,python,multithreading,iterator,generator,multicore,Python,Multithreading,Iterator,Generator,Multicore,在当今多核、多线程CPU的编程世界中(我笔记本中的CPU有两个内核,每个内核有两个线程),编写能够利用提供的硬件功能的代码变得越来越有意义。像go(lang)这样的语言诞生是为了让程序员更容易通过生成多个“独立”进程来加速应用程序,以便以后再次同步它们 在这种情况下,在接触Python中的生成器函数时,我预计这些函数将使用后续项目请求之间传递的空闲时间来准备立即交付的下一个收益,但似乎不是这样——至少我对运行下面提供的代码得到的结果的解释是这样的 更让我困惑的是,生成器函数的调用方必须等到函数完

在当今多核、多线程CPU的编程世界中(我笔记本中的CPU有两个内核,每个内核有两个线程),编写能够利用提供的硬件功能的代码变得越来越有意义。像go(lang)这样的语言诞生是为了让程序员更容易通过生成多个“独立”进程来加速应用程序,以便以后再次同步它们

在这种情况下,在接触Python中的生成器函数时,我预计这些函数将使用后续项目请求之间传递的空闲时间来准备立即交付的下一个收益,但似乎不是这样——至少我对运行下面提供的代码得到的结果的解释是这样的

更让我困惑的是,生成器函数的调用方必须等到函数完成处理所有剩余指令,即使生成器已经交付了所有项

有什么我目前看不到的明确原因吗?为什么是发电机 函数不在两个产生请求之间的空闲时间内运行代码 超过要求的产量,直到满足下一个产量指令,以及 如果所有项目都已交付,甚至让调用者等待

下面是我使用的代码:

import time
startTime = time.time()
time.sleep(1)
def generatorFunctionF():
    print("# here: generatorFunctionF() lineNo #1", time.time()-startTime)
    for i in range(1,4):
        print("# now: time.sleep(1)", time.time()-startTime)
        time.sleep(1)
        print("# before yield", i, time.time()-startTime)
        yield i # yield i
        print("# after  yield", i, time.time()-startTime)
    print("# now: time.sleep(5)", time.time()-startTime)
    time.sleep(5)
    print("# end followed by 'return'", time.time()-startTime)
    return
#:def

def standardFunctionF():
    print("*** before: 'gFF = generatorFunctionF()'", time.time()-startTime) 
    gFF = generatorFunctionF()
    print("*** after:  'gFF = generatorFunctionF()'", time.time()-startTime) 
    print("*** before print(next(gFF)", time.time()-startTime)
    print(next(gFF))
    print("*** after  print(next(gFF)", time.time()-startTime)
    print("*** before time.sleep(3)", time.time()-startTime)
    time.sleep(3)
    print("*** after  time.sleep(3)", time.time()-startTime)
    print("*** before print(next(gFF)", time.time()-startTime)
    print(next(gFF))
    print("*** after  print(next(gFF)", time.time()-startTime)
    print("*** before list(gFF)", time.time()-startTime)
    print("*** list(gFF): ", list(gFF), time.time()-startTime)
    print("*** after:  list(gFF)", time.time()-startTime)
    print("*** before time.sleep(3)", time.time()-startTime)
    time.sleep(3)
    print("*** after  time.sleep(3)", time.time()-startTime)
    return "*** endOf standardFunctionF"

print()
print(standardFunctionF)
print(standardFunctionF())
给出:

>python3.6 -u "aboutIteratorsAndGenerators.py"

<function standardFunctionF at 0x7f97800361e0>
*** before: 'gFF = generatorFunctionF()' 1.001169204711914
*** after:  'gFF = generatorFunctionF()' 1.0011975765228271
*** before print(next(gFF) 1.0012099742889404
# here: generatorFunctionF() lineNo #1 1.0012233257293701
# now: time.sleep(1) 1.0012412071228027
# before yield 1 2.0023491382598877
1
*** after  print(next(gFF) 2.002397298812866
*** before time.sleep(3) 2.0024073123931885
*** after  time.sleep(3) 5.005511283874512
*** before print(next(gFF) 5.005547761917114
# after  yield 1 5.005556106567383
# now: time.sleep(1) 5.005565881729126
# before yield 2 6.006666898727417
2
*** after  print(next(gFF) 6.006711006164551
*** before list(gFF) 6.0067174434661865
# after  yield 2 6.006726026535034
# now: time.sleep(1) 6.006732702255249
# before yield 3 7.0077736377716064
# after  yield 3 7.0078125
# now: time.sleep(5) 7.007838010787964
# end followed by 'return' 12.011908054351807
*** list(gFF):  [3] 12.011950254440308
*** after:  list(gFF) 12.011966466903687
*** before time.sleep(3) 12.011971473693848
*** after  time.sleep(3) 15.015069007873535
*** endOf standardFunctionF
>Exit code: 0
python3.6-u“aboutIteratorsAndGenerators.py” ***之前:“gFF=generatorFunctionF()”1.001169204711914 ***“gFF=generatorFunctionF()”1.0011975765228271之后 ***打印前(下一个(gFF)1.0012099742889404 #此处:generatorFunctionF()行号#1 1.0012233257293701 #现在:时间。睡眠(1)1.0012412071228027 #收益率前12.0023491382598877 1. ***打印后(下一个(gFF)2.002397298812866 ***时间之前。睡眠(3)2.0024073123931885 ***时间过后。睡眠(3)5.005511283874512 ***打印前(下一个(gFF)5.005547761917114 #后收益率1 5.005556106567383 #现在:时间。睡眠(1)5.005565881729126 #收益率前2 6.006666898727417 2. ***打印后(下一个(gFF)6.006711006164551 ***列表前(gFF)6.0067174434661865 #后收益率2 6.006726026535034 #现在:时间。睡眠(1)6.006732702255249 #收益率3之前7.0077736377716064 #收益率37.0078125后 #现在:时间。睡眠(5)7.007838010787964 #结束后接“返回”12.011908054351807 ***清单(gFF):[3]12.011950254440308 ***之后:列表(gFF)12.011966466903687 ***时间之前。睡眠(3)12.011971473693848 ***时间过后。睡眠(3)15.015069007873535 ***标准函数 >退出代码:0
因为代码之间的收益率可能会有副作用。不只是在“需要下一个值”时,才推进生成器但是当您想通过继续运行代码来推进生成器时。

关于Python中生成器函数的预期特性的问题应该从更广泛的主题的角度来看待

隐式并行

在计算机科学中,隐式并行是编程语言的一个特征,它允许编译器或解释器自动利用某些语言结构所表达的计算所固有的并行性

问题的实质是,有没有什么重要的原因,为什么一个生成器函数不在两次生成之间的空闲时间内预取下一项?实际上是问

“Python作为编程语言是否支持隐式并行?”

尽管事实上(问题作者引用的观点):“没有任何合理的理由说明生成器函数不应该提供这种‘智能’行为”。在Python作为编程语言的上下文中,这是对问题的实际正确答案(已在评论中给出,但未明确揭示问题的核心)是:

Python生成器函数不应该在后台智能地预取下一项以便以后立即交付的重要原因是Python作为编程语言 不支持隐式并行。


也就是说,在本上下文中探索是否可以在Python中以显式方式提供预期功能确实很有趣?是的,这是可能的。在本上下文中,让我们演示一个生成器函数,通过将此功能显式编程到这样的函数中,它能够隐式预取后台的下一项:

from multiprocessing import Process
import time

def generatorFetchingItemsOnDemand():
    for i in range(1, 4):
        time.sleep(2)
        print("# ...ItemsOnDemand spends 2 seconds for delivery of item")
        yield i

def generatorPrefetchingItemsForImmediateDelivery():
    with open('tmpFile','w') as tmpFile:
        tmpFile.write('')
        tmpFile.flush()

    def itemPrefetcher():
        for i in range(1, 4):
            time.sleep(2)
            print("### itemPrefetcher spends 2 seconds for prefetching an item")
            with open('tmpFile','a') as tmpFile:
                tmpFile.write(str(i)+'\n')
                tmpFile.flush()

    p = Process(target=itemPrefetcher)
    p.start()

    for i in range(1, 4):
        with open('tmpFile','r') as tmpFile:
            lstFileLines = tmpFile.readlines()
        if len(lstFileLines) < i: 
            while len(lstFileLines) < i:
                time.sleep(0.1)
                with open('tmpFile','r') as tmpFile:
                    lstFileLines = tmpFile.readlines()

        yield int(lstFileLines[i-1])
#:def

def workOnAllItems(intValue):
    startTime = time.time()
    time.sleep(2)
    print("workOn(", intValue, "): took", (time.time()-startTime), "seconds")
    return intValue

print("===============================")        
genPrefetch = generatorPrefetchingItemsForImmediateDelivery()
startTime = time.time()
for item in genPrefetch:
    workOnAllItems(item)
print("using genPrefetch workOnAllItems took", (time.time()-startTime), "seconds")
print("-------------------------------")        
print()
print("===============================")        
genOnDemand = generatorFetchingItemsOnDemand()
startTime = time.time()
for item in genOnDemand:
    workOnAllItems(item)
print("using genOnDemand workOnAllItems took", (time.time()-startTime), "seconds")
print("-------------------------------")        

生成器被设计成一种更简单、更短、更容易理解的语法来编写迭代器。这就是它们的使用案例。想要使迭代器更短、更容易理解的人希望在他们编写的每个迭代器中引入线程同步的头痛问题。这与设计相反进球

因此,生成器基于协同多任务的概念,而不是线程。设计权衡是不同的;生成器牺牲并行执行来换取更容易推理的语义

此外,为每个生成器使用单独的线程将非常低效,并且确定何时并行是一个困难的问题。大多数生成器实际上不值得在另一个线程中执行。见鬼,即使在Python的无GIL实现中,如Jython或Grumpy,它们也不值得在另一个线程中执行


如果您想要并行运行,那么启动线程或进程并通过队列与之通信已经可以处理了。

不确定问题的第二部分(关于“必须等待生成器完成”)是什么意思。请澄清你的意思。不要忘记,
生成器
的相同机制可以是
协同程序
,例如,
x=yield 10
,这在
yield
ing
10
之后暂停,但分配发生在下一次
send(5)
next(…)
。您可能需要查看
asy
>python3.6 -u "generatorPrefetchingItemsForImmediateDelivery.py"
===============================
### itemPrefetcher spends 2 seconds for prefetching an item
### itemPrefetcher spends 2 seconds for prefetching an item
workOn( 1 ): took 2.0009119510650635 seconds
### itemPrefetcher spends 2 seconds for prefetching an item
workOn( 2 ): took 2.0010197162628174 seconds
workOn( 3 ): took 2.00161075592041 seconds
using genPrefetch workOnAllItems took 8.013896942138672 seconds
-------------------------------

===============================
# ...ItemsOnDemand spends 2 seconds for delivery of item
workOn( 1 ): took 2.0011563301086426 seconds
# ...ItemsOnDemand spends 2 seconds for delivery of item
workOn( 2 ): took 2.001920461654663 seconds
# ...ItemsOnDemand spends 2 seconds for delivery of item
workOn( 3 ): took 2.0002224445343018 seconds
using genOnDemand workOnAllItems took 12.007976293563843 seconds
-------------------------------
>Exit code: 0