• Python标准模块--concurrent.futures


    1 模块简介

    concurrent.futures模块是在Python3.2中添加的。根据Python的官方文档,concurrent.futures模块提供给开发者一个执行异步调用的高级接口。concurrent.futures基本上就是在Python的threading和multiprocessing模块之上构建的抽象层,更易于使用。尽管这个抽象层简化了这些模块的使用,但是也降低了很多灵活性,所以如果你需要处理一些定制化的任务,concurrent.futures或许并不适合你。

    concurrent.futures包括抽象类Executor,它并不能直接被使用,所以你需要使用它的两个子类:ThreadPoolExecutor或者ProcessPoolExecutor。正如你所猜的,这两个子类分别对应着Python的threading和multiprocessing接口。这两个子类都提供了池,你可以将线程或者进程放入其中。

    在计算机科学中,future有着特殊的含义。当使用concurrent技术时,它可以被用于同步操作。future也可以描述在任务结束之前,进程或者线程的结果。我喜欢将它看作即将发生的结果。

    2 模块使用

    2.1 创建池

    你可以通过concurrent.futures很容易地创建一个工作池。实例如下,

    import os
    import urllib.request
    
    from concurrent.futures import ThreadPoolExecutor
    from concurrent.futures import as_completed
    
    def downloader(url):
        req = urllib.request.urlopen(url)
        filename = os.path.basename(url)
        ext = os.path.splitext(url)[1]
        if not ext:
            raise RuntimeError("URL does not contain an extension")
    
        with open(filename,"wb") as file_handle:
            while True:
                chunk = req.read(1024)
                if not chunk:
                    break
                file_handle.write(chunk)
            msg = "Finished downloading {filename}".format(filename = filename)
            return msg
    
    def main(urls):
        with ThreadPoolExecutor(max_workers = 5) as executor:
            futures = [executor.submit(downloader,url) for url in urls]
            for future in as_completed(futures):
                print(future.result())
    
    if __name__ == "__main__":
        urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
        main(urls)
    

    首先,我们引入我们需要的模块。然后,我们创建downloader函数,会检查URL是否有扩展名,如果没有扩展名,我们就会抛出RuntimeError错误。然后,我们创建main函数,在这里,我们会实例化一个线程池。你可以在ThreadPoolExecutor和ProcessPoolExecutor上使用Python的with语句。

    我们设置线程池中工作线程数目为5。然后我们通过列表创建一组futures(或者为任务),最后,我们调用as_complete函数。这个函数是一个迭代器,当任务结束时,会返回任务。当它们完成时,我们将结果打印出来,结果就是我们的downloader函数返回的一个字符串。

    如果我们使用的函数是计算密集型的,我们可以使用ProcessPoolExecutor,替代ThreadPoolExecutor,仅仅需要修改一行代码。

    我们可以使用concurrent.futures中的map方法,让代码更加简洁。让我们将上述代码重构一下,如下所示,

    import os
    import urllib.request
    
    from concurrent.futures import ThreadPoolExecutor
    from concurrent.futures import as_completed
    
    def downloader(url):
        req = urllib.request.urlopen(url)
        filename = os.path.basename(url)
        ext = os.path.splitext(url)[1]
        if not ext:
            raise RuntimeError("URL does not contain an extension")
    
        with open(filename,"wb") as file_handle:
            while True:
                chunk = req.read(1024)
                if not chunk:
                    break
                file_handle.write(chunk)
            msg = "Finished downloading {filename}".format(filename = filename)
            return msg
    
    def main(urls):
        with ThreadPoolExecutor(max_workers = 5) as executor:
            return executor.map(downloader, urls ,timeout = 60)
    
    if __name__ == "__main__":
        urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
                "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
        results = main(urls)
        for result in results:
            print(result)
    

    主要的区别在main函数中,已经被减少到两行代码。concurrent.futures中的map方法类似于Python中的map方法,它获取一个函数和一个可迭代对象,然后在可迭代对象上每个元素上依次调用这个函数。你也可以在你的每个线程中加入timeout,如果某一个线程挂掉,整个程序就会停止。最后,在Python3.5中,他们添加了chunksize变量,在很大的可迭代对象上使用线程池,可以改善性能。如果你使用的进程池,chunksize不会起作用。

    2.2 死锁

    concurrent.futures模块有一个缺陷,当调用一个关联任务,这个任务又在等待另一个任务时,你就会进入死锁。这个听起来很令人困惑,让我们看一个实例,

    from concurrent.futures import ThreadPoolExecutor
    
    def wait_forever():
        my_future = executor.submit(zip,[1,2,3],[4,5,6])
        result = my_future.result()
        print(result)
    
    if __name__ == "__main__":
        executor = ThreadPoolExecutor(max_workers = 1)
        executor.submit(wait_forever)
    

    首先,我们引入ThreadPoolExecutor类,并实例化它。需要注意的是,我们设置最大的工作进程数目为1。然后,我们注册wait_forever函数。在wait_forever函数中,我们向线程池中注册了另一个任务--将两个列表打包在一起,获得这个操作的结果,并将结果打印出出来。但是,我们却创建了一个死锁。原因就是我们有一个任务等待另一个任务结束,也就是我们希望一个未完成的操作去等待另一个未完成的无效操作。

    让我们将它重写,如下所示,

    from concurrent.futures import ThreadPoolExecutor
    
    def wait_forever():
        my_future = executor.submit(zip,[1,2,3],[4,5,6])
        return my_future
    
    if __name__ == "__main__":
        executor = ThreadPoolExecutor(max_workers = 3)
        fut = executor.submit(wait_forever)
        result = fut.result()
        print(list(result.result()))
    

    在这里,我们仅仅返回函数内部的任务,然后获取它的结果。在我们返回的任务上调用result()方法的结果就会包含我们想要的结果,看起来似乎有些令人困惑。无论如何,如果我们在这个任务上调用result()方法,我们就会获得一个打包的对象,为了了解实际的结果就是是什么,我们使用Python的list函数将打包对象进行包裹,然后打印出来。

    3 Reference

    Python 201

  • 相关阅读:
    潭州课堂25班:Ph201805201 第十二课 new方法,定制属性访问,描述符与装饰器 (课堂笔记)
    潭州课堂25班:Ph201805201 第十一课 继承,多继承和魔术方法,属性和方法 (课堂笔记)
    Storm笔记
    java代码。继承。。。很戳我的心啊。。不太懂。super的真正用法
    java代码。从来没想过java里的继承是多么的难懂。如哲学
    java代码继承疑惑,请有心人解答
    java冒泡排序
    java数组复制
    java继承。顾不了
    java继承初级
  • 原文地址:https://www.cnblogs.com/zhbzz2007/p/6067068.html
Copyright © 2020-2023  润新知