为什么一个工作线程的ThreadPoolExecutor仍然比正常执行速度快?

时间:2016-11-09 14:30:00

标签: python multithreading python-3.x asynchronous threadpoolexecutor

我正在使用此库Tomorrow,而该库又使用标准库中的ThreadPoolExecutor,以便允许异步函数调用。

调用装饰器@tomorrow.threads(1)使用1个工作器旋转ThreadPoolExecutor。

问题

  • 为什么使用1 thread worker执行函数而不是按原样调用它(例如正常情况下)会更快?
  • 为什么用10 thread workers代替1代表相同的代码执行相同的代码会更慢,甚至没有呢?

演示代码

导入已排除

def openSync(path: str):
    for row in open(path):
        for _ in row:
            pass

@tomorrow.threads(1)
def openAsync1(path: str):
    openSync(path)

@tomorrow.threads(10)
def openAsync10(path: str):
    openSync(path)

def openAll(paths: list):
    def do(func: callable)->float:
        t = time.time()
        [func(p) for p in paths]
        t = time.time() - t
        return t
    print(do(openSync))
    print(do(openAsync1))
    print(do(openAsync10))

openAll(glob.glob("data/*"))

注意:data文件夹包含18个文件,每700行随机文本。

输出

0名工人:0.0120
1名工人 0.0009
10名员工 0.0535

我测试了什么

  • 我已经运行了几次以上的代码,不同的程序在后台运行(昨天运行一堆,今天运行一对)。数字改变,ofc,但顺序总是相同的。 (即1是最快的,然后是0然后是10)。
  • 我还尝试更改执行顺序(例如,移动do调用)以消除缓存作为一个因素,但仍然相同。
    • 事实证明,按照101None的顺序执行会产生不同的顺序(1最快,然后是10,然后是0)与其他所有排列相比。结果表明,无论最后执行do调用,都会比先执行或在中间执行时慢得多。

结果(从@Dunes收到解决方案后)

0名工人:0.0122
1名工人:0.0214
10名工人:0.0296

1 个答案:

答案 0 :(得分:1)

当您调用其中一个异步函数时,它会返回一个“期货”对象(在这种情况下为tomorrow.Tomorrow的实例)。这使您可以提交所有作业,而无需等待它们完成。但是,永远不要等待工作完成。所以do(openAsync1)所做的就是设置所有作业需要多长时间(应该非常快)。要获得更准确的测试,您需要执行以下操作:

def openAll(paths: list):
    def do(func: callable)->float:
        t = time.time()
        # do all jobs if openSync, else start all jobs if openAsync
        results = [func(p) for p in paths]
        # if openAsync, the following waits until all jobs are finished
        if func is not openSync:
            for r in results:
                r._wait()
        t = time.time() - t
        return t
    print(do(openSync))
    print(do(openAsync1))
    print(do(openAsync10))

openAll(glob.glob("data/*"))

在python中使用其他线程通常会减慢速度。这是因为全局解释器锁定意味着只有1个线程可以处于活动状态,无论CPU具有多少核心。

然而,由于你的工作是IO约束,事情变得复杂。更多工作线程可能加快速度。这是因为单个线程可能花费更多时间等待硬盘驱动器响应,而不是在多线程变体中各个线程之间的上下文切换之间丢失。

旁注,即使openAsync1openAsync10都没有等待作业完成,do(openAsync10)可能会更慢,因为在提交新作业时需要线程之间更多同步。