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

2024-10-02 22:26:20 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

更让我困惑的是,生成器函数的调用者必须等到函数完成后才能处理所有剩余的指令,即使生成器已经传递了所有项目。在

Are there any clear reasons I can't currently see, why a generator function doesn't in the idle time between yield requests run the code past the requested yield until it meets next yield instruction and even lets the caller wait in case all the items are already delivered?

这里是我使用的代码:

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())

给出:

^{pr2}$

Tags: the函数代码timesleeplistnextgff
3条回答

生成器被设计成更简单、更短、更易于理解的编写迭代器的语法。这是他们的用例。想要使迭代器变得更短、更容易理解的人不想把线程同步的问题引入他们编写的每个迭代器中。这将与设计目标相反。在

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

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

如果您想要并行运行的东西,那么已经可以通过启动线程或进程并通过队列与它通信来处理这个问题。在

因为收益率之间的代码可能有副作用。您不仅在“需要下一个值”时推进生成器,还需要通过继续运行代码来推进生成器。在

关于Python中生成器函数的预期特性的问题应该从

implicit parallelism

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

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

"Does Python as programming language support implicit parallelism?"

尽管事实上(问题的作者引用了作者的观点):“没有任何合理的理由说明生成器函数不应该提供这种‘智能’行为。“,在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("               -")        

提供的代码使用文件系统进行进程间通信,因此,如果您希望在自己的编程中重用此概念,用现有的其他更快的进程间通信机制来替换它,请随意使用。按照此处演示的方式实现生成器函数,执行问题作者期望的生成器函数应该做的事情,并有助于加快应用程序的速度(从12秒降到8秒):

^{pr2}$

相关问题 更多 >