为什么只有一个 worker 的 ThreadPoolExecutor 仍然比正常执行速度快?

Why is a ThreadPoolExecutor with one worker still faster than normal execution?

我正在使用这个库,Tomorrow, that in turn uses the ThreadPoolExecutor 来自标准库,以允许异步函数调用。

调用装饰器 @tomorrow.threads(1) 启动一个带有 1 个 worker 的 ThreadPoolExecutor。

问题

演示代码

排除进口

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行运行dom文本。

输出

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

我测试了什么

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

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

当您调用其中一个异步函数时,它 returns 一个 "futures" 对象(在本例中为 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) 可能更慢,因为它在提交新作业时需要线程之间的更多同步。