python 多线程(FIle read I/O)不是奇视吗?

python multithread ( FIle read I/O ) is not parallism?

提问人:coding-cat 提问时间:10/31/2023 更新时间:11/2/2023 访问量:62

问:

在 python 文档中,python 多线程值得执行 FILE I/O、网络 I/O、numpy 等任务

所以我尝试了下面的代码

import threading
import os
import time
import multiprocessing
# create test file

def create_test_file():
    if os.path.exists("./test.txt"):return 1
    with open("./test.txt","w") as fd :
        for i in range(100000):
            text = str(i) * 3
            text += "\n"
            fd.write(text)


create_test_file()

def file_read():
    for _ in range(100):
        with open("./test.txt","r") as fd :
            while True :
                line = fd.readline()
                if not line :  break



if __name__ == "__main__":
    start_time = time.time()
    file_read()
    file_read()
    file_read()
    file_read()
    print("sequential task %.2f"%(time.time() - start_time))

    t1 = threading.Thread(target=file_read)
    t2 = threading.Thread(target=file_read)
    t3 = threading.Thread(target=file_read)
    t4 = threading.Thread(target=file_read)

    start_time = time.time()

    t1.start()
    t2.start()
    t3.start()
    t4.start()

    t1.join()
    t2.join()
    t3.join()
    t4.join()

    print("multi_thread task %.2f"%(time.time() - start_time))


    p1 = multiprocessing.Process(target=file_read)
    p2 = multiprocessing.Process(target=file_read)
    p3 = multiprocessing.Process(target=file_read)
    p4 = multiprocessing.Process(target=file_read)

    start_time = time.time()

    p1.start()
    p2.start()
    p3.start()
    p4.start()

    p1.join()
    p2.join()
    p3.join()
    p4.join()

    print("multi_process task %.2f" % (time.time() - start_time))

但结果是

顺序任务 7.25 multi_thread任务 7.58 multi_process任务 2.45

为什么多线程效率不高?

我知道python有GIL,但是在python文档(https://wiki.python.org/moin/GlobalInterpreterLock )

事后看来,GIL 并不理想,因为它会阻止多线程 CPython 程序在某些情况下充分利用多处理器系统。幸运的是,许多潜在的阻塞或长时间运行的操作,例如 I/O、图像处理、

为什么多线程比顺序任务慢???GIL 仍在工作 文件读取 I/O??

python 多线程 文件-io 多处理 gil

评论

0赞 Aaron 10/31/2023
您的不会受到 IO 限制,而是会受到 CPU 限制,因为它一次只读取非常少量的数据(最多 16 个字符:-)。在内部,python 可能已经缓冲了比 16 个字符(默认缓冲区大小)多得多的读取read_file000\n999999999999999\n)
0赞 coding-cat 11/1/2023
@Aaron 谢谢你的回答,我修复了一些代码,我得到了改进的结果顺序任务 32.29 multi_thread任务 21.39 multi_process任务 8.65 但是我仍然有一些问题。似乎我的多线程代码无法并行利用 CPU 内核。如果使用得当,多线程时间和多进程时间不应该彼此接近吗?可能会有一点差异
0赞 Booboo 11/4/2023
许多操作系统(当然是 Windows)都有磁盘缓存,因此当您尝试多次读取同一文件时,只有第一次真正访问磁盘。因此,与磁盘 I/O 一样快,从内存中缓存读取的速度要快得多。因此,串行读取文件然后使用多线程的基准测试设计得并不好,因为第一次文件读取将比后续文件读取花费更长的时间。

答:

1赞 Aaron 11/2/2023 #1

顶级域名;file.read 确实释放了 gil,但很难让文件读取 Python 代码中的限制瓶颈。

每当你真的想知道用 c/c++ 编写的 python 函数(或库函数)是否释放 GIL 时;您查找宏:,并且确实在“FileUtils.c”中确实释放了GIL。现代操作系统和固态硬盘(甚至是硬盘)在从文件中读取数据的速度非常快,因此,如果这是程序中唯一可以利用并行性的部分,那么您真的不会看到线程的太多优势。这就是为什么使用它的原因,它可以让您绕过 GIL。Py_BEGIN_ALLOW_THREADS_Py_readmultiprocessing

但是,如果您从套接字而不是文件读取数据速率可能受到网络硬件或互联网服务提供商的限制,则该调用可能确实是限制因素,并且将其并行可能确实会有所帮助。最终,许多这些“等待 IO”任务都同样出色,基本上允许您对许多 IO 任务进行排队,然后在它们完成时响应它们。这是以显式单线程方式完成的,因此没有并行处理的错觉,只是您可以对可能需要一段时间才能响应的事情进行并行等待。readasyncio

numpy正如您在某些计算中提到的,也会释放 GIL,但应该注意的是,numpy 的底层 BLAS 库可能已经在使用多线程,并且通常很难通过在此基础上实现自己的多线程来提高性能。我最近观看的这段视频介绍了从一般矩阵乘法(点积)函数中实际提取最大性能所需的一些高级主题。它基本上以“只使用现有的库”结束,因为大量非常聪明的人已经将其优化到极致。在本例中,该库是“英特尔数学核心函数库”,它可能与numpy使用的库相同,具体取决于它的编译方式。

评论

0赞 coding-cat 11/4/2023
谢谢你的回答!这对我真的很有帮助