为什么在多线程Python应用程序中I / O与计算不重叠?

时间:2018-12-26 15:10:25

标签: python-3.x python-multithreading

我编写了一个简单的Python脚本来测试I / O绑定线程和cpu绑定线程之间的重叠。代码在这里:

from datetime import datetime
import threading
import shutil
import os


def cpuJob(start,end):
    counter=start
    sum=0
    while counter<=end:
        sum+=counter
        counter+=1
    return sum


def ioJob(from_path, to_path):
    if os.path.exists(to_path):
        shutil.rmtree(to_path)
    shutil.copytree(from_path, to_path)

startTime=datetime.now()

Max=120000000
threadCount=2

if threadCount==1:
    t1 = threading.Thread(target=cpuJob, args=(1,Max))
    # t1 = threading.Thread(target=ioJob, args=(1,Max))
    t1.start()
    t1.join()
else:
    t1 = threading.Thread(target=ioJob, args=("d:\\1","d:\\2"))
    t2 = threading.Thread(target=cpuJob, args=(1,Max))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

endTime=datetime.now()

diffTime = endTime - startTime

print("Execution time for " , threadCount , " threads is: " , diffTime)

如果我分别运行线程(threadCount == 1),则在Windows笔记本电脑上完成每个线程大约需要12-13秒。但是,当我将它们运行得很小时(threadCount == 2)时,大约需要20-22秒。据我所知,Python在执行任何阻塞的I / O操作之前会释放GIL。如果在使用I / O之前释放了GIL,为什么在代码中获得如此性能?

编辑1:如通信建议,我检查了shutils的代码。似乎在此程序包的实现中,未发布GIL。为什么会这样呢? shell实用程序包的代码应该不在Python运行时实现的范围内,不是吗?

2 个答案:

答案 0 :(得分:2)

  

...为什么我会得到这样的表现?

请参见https://docs.python.org/3/library/threading.html

  

CPython实现细节:在CPython中,由于具有全局解释器锁,因此只有一个线程可以一次执行Python代码(即使某些面向性能的库可能克服了此限制)。如果希望您的应用程序更好地利用多核计算机的计算资源,建议您使用 multiprocessing 或current.futures.ProcessPoolExecutor。但是,如果您要同时运行多个I / O绑定任务,则线程化仍然是合适的模型。

您的代码在非抢占式框架中运行,但是它永远不会产生控制权,直到退出。因此,直到那时才会安排另一个线程。您使用了一些线程机制,但是您可能还编写了两行顺序函数,该函数依次调用io_job()cpu_job()

您要寻找的是multiprocessing

此外,如果您确实想使用rsync之类的工具来复制文件树,请考虑使用gmake -jN或GNU parallel(sudo apt install parallel)。这是一个示例命令:

$ find . -name '*.txt' -type f | parallel gzip -v9

make和/ usr / bin / parallel都可以指定同时工作的工人数,并且每次工作人员完成任务时,都会继续从队列中绘制新任务。

答案 1 :(得分:1)

据我机器上的/usr/lib/python3.6/shutil.py看来,这些功能rmtreecopytree等已实现为_rmtree_unsafe之类的Python代码。 rmtree等后面的基础API类似于os.listdiros.unlink

由于Python GIL的限制,一次只能有一个线程可以运行Python代码。因此,cpuJobioJob不能同时(并行)运行,因为它们都是纯Python代码,因此当您尝试将它们作为“线程”运行时,不会观察到任何性能改进。

相关问题