协程与资源获取的结合似乎会带来一些意想不到的(或不直观的)后果。
基本的问题是这样的事情是否有效:
def coroutine():
with open(path, 'r') as fh:
for line in fh:
yield line
是的。 (您可以测试!)
更深层次的担忧是,应该将with
替换为finally
的一种替代方法,在该方法中,您要确保在块末释放资源。 协程可以从with
块中暂停并恢复执行,那么如何解决冲突?
例如,如果在协程尚未返回的情况下,在协程内部和外部都以读/写方式打开文件,则:
def coroutine():
with open('test.txt', 'rw+') as fh:
for line in fh:
yield line
a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
for line in fh:
print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?
在上一个示例中,我打算进行写锁定的文件句柄争用,但是由于大多数操作系统都按进程分配文件句柄,因此那里没有争用。 (@Miles指出该示例没有什么道理。)这是我修改后的示例,它显示了真正的死锁条件:
import threading
lock = threading.Lock()
def coroutine():
with lock:
yield 'spam'
yield 'eggs'
generator = coroutine()
assert generator.next()
with lock: # Deadlock!
print 'Outside the coroutine got the lock'
assert generator.next()
我已经读过在CPython中,解释器堆栈(为此目的而调用的Python函数的列表)与C堆栈(在解释器自己的代码中调用的C函数的列表)混合在一起。 如果是这样,那么如何实现生成器和协程? 他们如何记住执行状态? CPython是否将每个生成器/协程的堆栈复制到OS堆栈或从OS堆栈复制? 还是CPython只是将生成器的最高堆栈帧保留在堆上,因为生成器只能从该最高帧产生?
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
async def foo():
await time.sleep(1)
foo()
我无法使这个简单的示例运行:
RuntimeWarning: coroutine 'foo' was never awaited foo()
背景:
我之所以这么问,是因为我目前有一个具有许多(数百至数千)线程的应用程序。 这些线程中的大多数在大部分时间都处于空闲状态,等待将工作项放入队列中。 当工作项可用时,然后通过调用一些任意复杂的现有代码对其进行处理。 在某些操作系统配置上,应用程序会遇到控制最大用户进程数的内核参数,因此我想尝试减少工作线程数的方法。
我建议的解决方案:
似乎是基于协程的方法,用协程替换每个工作程序线程将有助于实现此目的。 然后,我可以有一个由实际(内核)工作线程池支持的工作队列。 将项目放置在特定协程的队列中进行处理时,会将条目放置在线程池的队列中。 然后它将恢复相应的协程,处理其排队的数据,然后再次将其暂停,以释放工作线程来执行其他工作。
实施细节:
在考虑如何执行此操作时,我很难理解无栈协程和有栈协程之间的功能差异。 我有一些使用Boost.Coroutine库使用堆栈协程的经验。 我发现从概念上理解相对容易:对于每个协程,它维护CPU上下文和堆栈的副本,并且当您切换到协程时,它将切换到该保存的上下文(就像内核模式调度程序那样 )。
我不太清楚的是无栈协程与此有何不同。 在我的应用程序中,与上述工作项排队相关的开销非常重要。 我见过的大多数实现(例如新的CO2库)都建议无堆栈协程提供开销更低的上下文切换。
因此,我想更清楚地了解无栈协程和有栈协程之间的功能差异。 具体来说,我想到了以下问题:
像这样的引用表明,区别在于您可以在有堆栈的协程与无堆栈的协程中屈服/恢复。 是这样吗 是否有一个简单的示例,说明我可以在堆栈式协程中执行某些操作,但不能在无堆栈的协程中执行某些操作?
使用自动存储变量(即“在堆栈上”的变量)是否有任何限制?
我可以从无堆栈协程调用哪些函数有任何限制?
如果没有为无堆栈协程保存堆栈上下文,那么当协程运行时,自动存储变量会移到哪里?
可以说我有两个功能:
def foo():
return 'foo'
def bar():
yield 'bar'
第一个是普通函数,第二个是生成器函数。 现在我要写这样的东西:
def run(func):
if is_generator_function(func):
gen = func()
gen.next()
#... run the generator ...
else:
func()
goo()
的简单实现将是什么样子? 使用goo()
程序包,我可以测试gen
是否为发电机,但是我希望在调用func()
之前先进行测试。
现在考虑以下情况:
def goo():
if False:
yield
else:
return
调用goo()
将返回一个生成器。 我假设python解析器知道goo()
函数具有yield语句,并且我想知道是否有可能轻松获得该信息。
谢谢!
Monads可以做很多令人惊奇的疯狂事情。 他们可以创建保存值叠加的变量。 它们可以让您在计算数据之前访问将来的数据。 它们可以允许您编写破坏性的更新,但实际上不是。 然后,延续monad可以让您大开眼界! 通常是您自己的。 ;-)
但这是一个挑战:您可以制作一个可以暂停的单子吗?
data Pause s x instance Monad (Pause s) mutate :: (s -> s) -> Pause s () yield :: Pause s () step :: s -> Pause s () -> (s, Maybe (Pause s ()))
yield
monad是一种状态monad(因此具有明显的语义step
)。 通常,像这样的monad具有某种“运行”功能,该功能运行计算并让您退回最终状态。 但是Pause
是不同的:它提供了step
函数,该函数运行计算直到调用神奇的yield
函数。 在这里,计算被暂停,向调用者返回足够的信息,以便稍后继续计算。
要获得更大的威力,请执行以下操作:允许呼叫者修改yield
呼叫之间的状态。 (例如,上面的类型签名应允许这样做。)
用例:编写执行复杂功能的代码通常很容易,但是使用一个完整的PITA对其进行转换以在其操作中也输出中间状态。 如果您希望用户能够在执行过程中进行某些更改,那么事情就会变得非常复杂。
实施思路:
显然,可以使用线程,锁和yield
来完成。但是我们可以做得更好吗? ;-)
用连续单调疯狂吗?
也许是某种作家monad,其中yield
只是记录当前状态,然后我们可以通过遍历日志中的状态来“假装”为step
。 (显然,这阻止了在步骤之间更改状态,因为我们现在并没有真正“暂停”任何内容。)
我有一些示例Python代码,我需要在C ++中模仿它。 我不需要任何特定的解决方案(例如基于协同例程的产量解决方案,尽管它们也是可接受的答案),我只需要以某种方式重现语义。
这是一个基本的序列生成器,显然太大而无法存储物化版本。
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
目标是维护上面序列的两个实例,并以半锁步方式迭代它们,但是以块为单位。 在下面的示例中,yield
使用对序列来初始化缓冲区,second_pass
重新生成相同的精确序列并再次处理缓冲区。
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
我在C ++中找到解决方案的唯一办法就是模仿yield
与C ++协同程序,但我没有找到任何关于如何做到这一点的好参考。 我也对这个问题的替代(非一般)解决方案感兴趣。 我没有足够的内存预算来保存传递之间序列的副本。
我理解协程的原理。 我知道如何让标准StartCoroutine
/IEnumerator
模式在Unity中的C#中工作,例如 通过StartCoroutine
调用返回WaitForSeconds
的方法并在该方法中执行某些操作,执行yield return new WaitForSeconds(1);
等待一秒,然后执行其他操作。
我的问题是:幕后真的发生了什么? StartCoroutine
到底怎么了? 什么IEnumerator
是WaitForSeconds
返回? StartCoroutine
如何将控制权返还给"其他内容" 被调用方法的一部分? 所有这些如何与Unity的并发模型(在不使用协同程序的情况下同时进行许多事情)进行交互?
在kotlinx.coroutines
库中,您可以使用launch
(join
)或async
(await
)启动新的协程。 他们之间有什么区别?
Python中_get_child_candidates
关键字的用途是什么? 它有什么作用?
例如,我试图理解这个代码1:
def _get_child_candidates(self, distance, min_dist, max_dist):
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
这是来电者:
result, candidates = [], [self]
while candidates:
node = candidates.pop()
distance = node._get_dist(obj)
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
当调用方法_get_child_candidates
时会发生什么?列表是否返回? 单个元素? 它又被召唤了吗? 后续通话何时停止?
1.代码来自Jochen Schulz(jrschulz),他为度量空间创建了一个很棒的Python库。 这是完整源代码的链接:模块mspace。