Python和Trio,生产者是消费者,如何在工作完成后优雅地退出?

Python和Trio,生产者是消费者,如何在工作完成后优雅地退出?,python,channel,consumer,producer,python-trio,Python,Channel,Consumer,Producer,Python Trio,我正在尝试使用trioanasks制作一个简单的网络爬虫程序。我使用托儿所一次启动两个爬虫程序,使用内存通道维护要访问的URL列表 每个爬虫程序接收该通道两端的克隆,因此它们可以抓取一个url(通过receive_通道)、读取、查找并添加要访问的新url(通过send_通道) async def main(): 发送通道,接收通道=trio。打开内存通道(math.inf) 与trio.open_托儿所()异步作为托儿所: 与发送通道、接收通道异步: 托儿所。尽快启动(爬虫程序,发送\u通道.c

我正在尝试使用
trio
an
asks
制作一个简单的网络爬虫程序。我使用托儿所一次启动两个爬虫程序,使用内存通道维护要访问的URL列表

每个爬虫程序接收该通道两端的克隆,因此它们可以抓取一个url(通过receive_通道)、读取、查找并添加要访问的新url(通过send_通道)

async def main():
发送通道,接收通道=trio。打开内存通道(math.inf)
与trio.open_托儿所()异步作为托儿所:
与发送通道、接收通道异步:
托儿所。尽快启动(爬虫程序,发送\u通道.clone(),接收\u通道.clone())
托儿所。尽快启动(爬虫程序,发送\u通道.clone(),接收\u通道.clone())
托儿所。尽快启动(爬虫程序,发送\u通道.clone(),接收\u通道.clone())
异步def爬虫程序(发送通道、接收通道):
接收通道中url的异步:#我是消费者!
内容=等待。。。
找到的URL=。。。
对于找到的URL中的u,请执行以下操作:
等待send_频道。send(u)#我也是制片人!
在这种情况下,消费者就是生产者。如何优雅地停止一切?

关闭所有设备的条件是:

  • 频道是空的
  • 所有爬虫都被卡在第一个for循环中,等待url出现在receive_频道中(这…不会再发生了)

我尝试了
async with send\u channel
inside
crawler()
,但没有找到一个好方法。我还试图找到一些不同的方法(一些内存通道绑定的工作池,等等),这里也没有运气。

这里至少有两个问题

首先,假设通道为空时停止。由于分配的内存通道大小为0,因此它将始终为空。只有当爬虫准备好接收url时,您才能传递url

这就产生了第二个问题。如果找到的URL比分配的爬虫多,则应用程序将死锁

原因是,由于您无法将找到的所有url传递给爬虫程序,爬虫程序将永远不会准备好接收新的url进行爬网,因为它会一直等待另一个爬虫获取其url之一

这变得更糟,因为假设其他爬虫中的一个找到了新的URL,它们也会被困在已经在等待交出其URL的爬虫后面,并且永远无法获取正在等待处理的URL之一

文件的相关部分:

假设我们解决了这个问题,下一步该怎么办

您可能需要保留一个所有已访问URL的列表(set?),以确保不再访问它们

为了确定何时停止,而不是关闭频道,简单地取消托儿所可能要容易得多

假设我们修改主循环如下:

async def main():
发送通道,接收通道=trio。打开内存通道(math.inf)
活跃工人=三人。容量限制(3)#工人数量
与trio.open_托儿所()异步作为托儿所:
与发送通道、接收通道异步:
托儿所。尽快启动(爬虫程序、活动工作者、发送通道、接收通道)
托儿所。尽快启动(爬虫程序、活动工作者、发送通道、接收通道)
托儿所。尽快启动(爬虫程序、活动工作者、发送通道、接收通道)
尽管如此:
等待三人。睡眠(1)#给工人一个启动的机会。
如果活动的\u workers.借入的\u令牌==0,并且发送\u channel.statistics()。当前的\u缓冲区\u used==0:
托儿所。取消范围。取消()#全部完成!
现在我们需要稍微修改爬虫,以便在活动时拾取令牌

异步def爬虫程序(活动\u工作线程、发送\u通道、接收\u通道):
接收通道中url的异步:#我是消费者!
对于活跃的工作人员:
内容=等待。。。
找到的URL=。。。
对于找到的URL中的u,请执行以下操作:
等待send_频道。send(u)#我也是制片人!

其他要考虑的事情-< /P>


您可能需要在爬虫程序中使用
send\u频道。send\u noblock(u)
。由于您有一个无界的缓冲区,所以不可能出现WouldBlock异常,并且可能需要在每次发送时都没有检查点触发器的行为。这样,您就可以肯定地知道,在其他任务有机会获取新url或父任务有机会检查工作是否完成之前,特定url已被完全处理,并且所有新url都已添加。

这是我在尝试重新组织问题时提出的解决方案:

async def main():
发送通道,接收通道=trio。打开内存通道(math.inf)
极限=三个电容限位器(3)
与发送通道异步:
等待发送通道。发送('https://start-url,发送_channel.clone())
#此处1
与trio.open_托儿所()异步作为托儿所:
url异步,接收通道中的发送通道:#此处3
托儿所启动(消费者、url、发送频道、限制)
异步def爬虫程序(url、发送通道、限制、任务状态):
与限制异步,发送\u通道:
内容=等待。。。
链接=。。。
对于链接中的链接:
等待发送\u channel.send((链接,发送\u channel.clone()))
#这里2
(我跳过了跳过已访问的URL)

在这里,没有3个长寿消费者,但只要有足够的工作给他们,就最多有3个消费者

在#此处1,发送#通道关闭(因为它被用作上下文管理器),唯一使通道保持活动状态的是该通道内的一个克隆

在#HERE2,克隆也被关闭(因为上下文管理器)。如果通道为空,则该克隆是k的最后一个对象