我编写了一个简单的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运行时实现的范围内,不是吗?
答案 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
看来,这些功能rmtree
,copytree
等已实现为_rmtree_unsafe
之类的Python代码。 rmtree
等后面的基础API类似于os.listdir
和os.unlink
。
由于Python GIL的限制,一次只能有一个线程可以运行Python代码。因此,cpuJob
和ioJob
不能同时(并行)运行,因为它们都是纯Python代码,因此当您尝试将它们作为“线程”运行时,不会观察到任何性能改进。