我是否过度生成了 IO 绑定的网络抓取线程?
Am I overspawning IO bound web scraping threads?
TL;DR:
如果我产生 10 个 Web 请求,每个请求都在自己的线程上,并且 CPU 有 4 个线程限制,这是可以的还是低效的?线程是 IO 绑定的,因此在等待服务器响应时处于空闲状态(我相信)。 CPU同时超过4个线程return如何处理?
我有一个脚本,当前为我需要通过 http.client.HTTPSConnection
下载的每个文件(每个文件都位于唯一的 URL)启动一个新线程。最多,我可能需要生成 730 个线程。我已经这样做了,因为线程都是 IO 绑定工作(下载并保存到文件),但我不确定它们是并行执行还是 CPU 一次只执行一组。文件大小介于 20MB 到 110MB 之间的总 运行 时间大约为 15 分钟。
我的 CPU 是没有超线程的四核。这意味着它在任何给定时间只能同时支持 4 个线程。由于工作是 IO 绑定而不是 CPU 绑定,我是否仍然受限于只有 4 个并发线程?
我想令人困惑的是,如果说我在 10 个线程上只发送 1 个请求,我不确定会发生什么事件;如果他们同时 return 会怎样?或者 CPU 如何选择在进入下一个可用线程之前完成哪 4 个?
在所有这一切之后,如果 CPU 一次只处理 4 个线程,我想它仍然很聪明,因为我需要生成尽可能多的 IO 线程(因为它们会闲置,而等待服务器响应)对吗?
您可以在四核 CPU 上拥有明显多于 4 个 IO 绑定线程。但是,您确实希望有一些最大值。即使是 IO 绑定进程有时也会使用 CPU。例如,当收到一个数据包时,需要处理该数据包以更新 TCP 状态。如果您正在从套接字读取并写入文件,在大多数情况下,需要一些 CPU 才能将字符从套接字缓冲区实际复制到文件缓冲区。如果您使用 TLS,通常需要 CPU 来解密和加密数据。因此,即使是主要执行 IO 的线程也会使用 CPU 一些。最终,您使用 CPU 的一小部分时间将加起来并消耗可用的 CPU 资源。
另外请注意,在 Python 中,由于全局解释器锁,您一次只能有一个线程使用 CPU 到 运行 python 代码。因此,在执行诸如等待传出连接之类的操作时,通常不会保留 GIL。在那段时间里,其他线程可能 运行。但是,在从套接字或文件读取和写入时的某些时间段内,GIL 将被保留。对于最常见的工作负载,当您的线程需要一个 CPU 的时间部分达到一个完整的 CPU 而不是四个完整的 CPU 时,您的应用程序的性能可能会达到最大值。
您可能会发现使用 asyncio
或其他一些事件驱动架构可提供更好的性能。如果为真,这通常是因为事件驱动模型更擅长减少资源的跨线程争用。
针对您的问题编辑,我认为 10 个线程不会成为问题
TL;DR: 如果我产生 10 个 Web 请求,每个请求都在自己的线程上,并且 CPU 有 4 个线程限制,这是可以的还是低效的?线程是 IO 绑定的,因此在等待服务器响应时处于空闲状态(我相信)。 CPU同时超过4个线程return如何处理?
我有一个脚本,当前为我需要通过 http.client.HTTPSConnection
下载的每个文件(每个文件都位于唯一的 URL)启动一个新线程。最多,我可能需要生成 730 个线程。我已经这样做了,因为线程都是 IO 绑定工作(下载并保存到文件),但我不确定它们是并行执行还是 CPU 一次只执行一组。文件大小介于 20MB 到 110MB 之间的总 运行 时间大约为 15 分钟。
我的 CPU 是没有超线程的四核。这意味着它在任何给定时间只能同时支持 4 个线程。由于工作是 IO 绑定而不是 CPU 绑定,我是否仍然受限于只有 4 个并发线程?
我想令人困惑的是,如果说我在 10 个线程上只发送 1 个请求,我不确定会发生什么事件;如果他们同时 return 会怎样?或者 CPU 如何选择在进入下一个可用线程之前完成哪 4 个?
在所有这一切之后,如果 CPU 一次只处理 4 个线程,我想它仍然很聪明,因为我需要生成尽可能多的 IO 线程(因为它们会闲置,而等待服务器响应)对吗?
您可以在四核 CPU 上拥有明显多于 4 个 IO 绑定线程。但是,您确实希望有一些最大值。即使是 IO 绑定进程有时也会使用 CPU。例如,当收到一个数据包时,需要处理该数据包以更新 TCP 状态。如果您正在从套接字读取并写入文件,在大多数情况下,需要一些 CPU 才能将字符从套接字缓冲区实际复制到文件缓冲区。如果您使用 TLS,通常需要 CPU 来解密和加密数据。因此,即使是主要执行 IO 的线程也会使用 CPU 一些。最终,您使用 CPU 的一小部分时间将加起来并消耗可用的 CPU 资源。
另外请注意,在 Python 中,由于全局解释器锁,您一次只能有一个线程使用 CPU 到 运行 python 代码。因此,在执行诸如等待传出连接之类的操作时,通常不会保留 GIL。在那段时间里,其他线程可能 运行。但是,在从套接字或文件读取和写入时的某些时间段内,GIL 将被保留。对于最常见的工作负载,当您的线程需要一个 CPU 的时间部分达到一个完整的 CPU 而不是四个完整的 CPU 时,您的应用程序的性能可能会达到最大值。
您可能会发现使用 asyncio
或其他一些事件驱动架构可提供更好的性能。如果为真,这通常是因为事件驱动模型更擅长减少资源的跨线程争用。
针对您的问题编辑,我认为 10 个线程不会成为问题