在许多对象上并行调用方法

时间:2018-10-21 18:04:48

标签: python multithreading multiprocessing

我想第一次在Python中使用并发。因此,我开始阅读有关Python并发性的大量文章(GIL,线程与进程,多处理与并发,未来与……),并看到了许多令人费解的示例。即使在使用高级并发功能库的示例中。

所以我决定开始尝试一些东西,并对最终得到的非常非常简单的代码感到惊讶:

from concurrent.futures import ThreadPoolExecutor

class WebHostChecker(object):
    def __init__(self, websites):
        self.webhosts = []
        for website in websites:
            self.webhosts.append(WebHost(website))

    def __iter__(self):
        return iter(self.webhosts)

    def check_all(self):
        # sequential:
        #for webhost in self:
        #    webhost.check()
        # threaded:
        with ThreadPoolExecutor(max_workers=10) as executor:
            executor.map(lambda webhost: webhost.check(), self.webhosts)


class WebHost(object):
    def __init__(self, hostname):
        self.hostname = hostname

    def check(self):
        print("Checking {}".format(self.hostname))
        self.check_dns() # only modifies internal state, i.e.: sets self.dns
        self.check_http() # only modifies internal status, i.e.: sets self.http

使用这些类如下:

webhostchecker = WebHostChecker(["urla.com", "urlb.com"])
webhostchecker.check_all() # -> this calls .check() on all WebHost instances in parallel

相关的多处理/线程代码仅 3行。我几乎不必修改现有代码(我希望能够在第一次开始编写用于顺序执行的代码时做到这一点,但是在在线阅读了许多示例后开始对此表示怀疑。)

然后...它有效! :)

它可以完美地在多个线程之间分配IO等待,并且运行时间少于原始程序的1/3。

所以,现在,我的问题:

  • 我在这里想念什么?
  • 我可以用不同的方式实现吗? (我可以吗?)
  • 为什么其他例子如此复杂? (尽管我必须说我找不到在多个对象上进行方法调用的确切示例)
  • 当我使用无法立即预测的功能/代码扩展程序时,此代码会给我带来麻烦吗?
  • 我想我已经知道一个潜在的问题,如果有人可以证实我的理由,那就太好了:如果WebHost.check()也受CPU限制,我将无法将ThreadPoolExecutor交换为ProcessPoolExecutor。因为每个进程都会获得WebHost实例的克隆版本?而且我将不得不编写一些代码,以将这些克隆的实例同步回原始实例?

任何能使我加深了解的见解/评论/评论/改进/ ...将不胜感激! :)

1 个答案:

答案 0 :(得分:0)

好,所以我要添加自己的第一个陷阱:

如果webhost.check()引发异常,则线程仅结束,并且可能未设置self.dns和/或self.http。但是,使用当前代码,您将看不到异常,除非您还访问executor.map()结果!让我想知道为什么某些对象在运行check_all()后会引发AttributeErrors:

只需评估每个结果即可轻松解决此问题(始终为None,因为我不让.check()返回任何内容)。您可以在所有线程都已运行或运行期间执行此操作。我选择在此期间(即在with语句内)引发异常,因此程序在出现第一个意外错误时停止:

def check_all(self):
    with ThreadPoolExecutor(max_workers=10) as executor:
        # this alone works, but does not raise any exceptions from the threads:
        #executor.map(lambda webhost: webhost.check(), self.webhosts)
        for i in executor.map(lambda webhost: webhost.check(), self.webhosts):
            pass

我想我也可以使用list(executor.map(lambda webhost:webhost.check(),self.webhosts)),但这会不必要地消耗内存。