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

标签 python multithreading python-3.x asynchronous threadpoolexecutor

我正在使用这个库,Tomorrow ,依次使用 ThreadPoolExecutor来自标准库,以允许异步函数调用。

调用装饰器 @tomorrow.threads(1) 会启动一个带有 1 个工作线程的 ThreadPoolExecutor。

问题

  • 为什么使用 1 个线程工作线程 执行函数比直接调用函数(例如通常)更快?
  • 为什么使用 10 个线程工作线程(而不是仅使用 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

我测试过的内容

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

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

0 个工作人员: 0.0122
1 个工作人员: 0.0214
10 个工作人员: 0.0296

最佳答案

当您调用其中一个异步函数时,它会返回一个“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 中使用额外的线程通常会减慢速度。这是因为全局解释器锁,这意味着无论 CPU 有多少个核心,都只能有 1 个线程处于事件状态。

但是,由于您的工作受 IO 限制,因此事情变得复杂。更多工作线程可能会加快速度。这是因为单个线程等待硬盘响应的时间可能比多线程变体中各个线程之间的上下文切换所损失的时间还要多。

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

关于python - 为什么只有一个工作线程的 ThreadPoolExecutor 仍然比正常执行速度更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40509371/

相关文章:

python-3.x - Altair 通过绑定(bind)范围 slider 对日期时间值进行选择和转换过滤器似乎不适用于相等条件或选择器本身

python - 等待使用 boto3 完全删除 DynamoDB 表

python - 使用 pandas 创建虚拟变量时,Jupyter notebook 内核死机

c++ - 从容器中移动 unique_ptr 并将其删除是否安全?

multithreading - CoreAnimation 警告删除了未提交 CATransaction 的线程

python - 添加不属于模型的自定义表单字段 (Django)

django - 如何在 Django 中验证 OAuth2 状态?

python - 在 Matplotlib 中更新单条

python - 字典中特定键的总和值

java - Thread.sleep 是模拟 HTTP 请求以进行测试的好方法吗?