Python 中文件 I/O 操作会释放 GIL 吗?

Mor*_*rtz 3 python concurrency multithreading gil python-3.x

根据我所读到的内容(例如此处),我了解 I/O 操作释放了 GIL。因此,如果我必须读取本地文件系统上的大量文件,我的理解是线程执行应该加快速度。

\n

为了测试这个 - 我有一个文件夹(input ),其中包含大约 100k 个文件 - 每个文件只有一行,其中包含一个随机整数。我有两个函数 - 一个“顺序”和一个“并发”,只需添加所有数字

\n
import glob\nimport concurrent.futures\nALL_FILES = glob.glob(\'./input/*.txt\')\n  \ndef extract_num_from_file(fname):\n    #time.sleep(0.1)\n    with open(fname, \'r\') as f:\n        file_contents = int(f.read().strip())\n    return file_contents\n\ndef seq_sum_map_based():\n   return sum(map(extract_num_from_file, ALL_FILES)) \n\ndef conc_sum_map_based():\n    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:\n        return sum(executor.map(extract_num_from_file, ALL_FILES))\n
Run Code Online (Sandbox Code Playgroud)\n

虽然这两个函数给出了相同的结果 - “并发”版本大约慢 3-4 倍。

\n
In [2]: %timeit ss.seq_sum_map_based()                                                                                                     \n3.77 s \xc3\x82\xc2\xb1 50.2 ms per loop (mean \xc3\x82\xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\nIn [3]: %timeit ss.conc_sum_map_based()                                                                                                    \n12.8 s \xc3\x82\xc2\xb1 240 ms per loop (mean \xc3\x82\xc2\xb1 std. dev. of 7 runs, 1 loop each)\n
Run Code Online (Sandbox Code Playgroud)\n

我的代码或我的理解有问题吗?

\n

B. *_*ing 6

注意:以下内容仅适用于 HDD,其具有可能影响读取吞吐量的移动部件,不适用于 SDD。巨大的性能差异的本质让我清楚地知道这是一个面向 HDD 的问题,因此此信息是在该假设下运行的。

问题在于,虽然线程可以并行操作,但由于只有单个读头,因此必须从硬盘驱动器顺序读取数据。然而,更糟糕的是,由于您已经并行化了 I/O 操作,底层操作系统将调度这些 I/O 任务,以便在切换到另一个线程之前仅部分处理这些文件——毕竟,即使您只有单个整数,文件头仍然需要处理 - 导致读取头比严格顺序的代码更疯狂地跳跃。与简单地按顺序读取每个文件的整体相比,所有这些都会导致开销大幅增加,而这不需要那么多的跳转。

例如,如果您有一个线程从磁盘加载大量数据,而第二个线程对其执行一些时间密集型处理,那么这不会是一个大问题,因为这将允许时间密集型处理继续不受 I/O 操作的阻塞。您的特定场景只是一个非常非常糟糕的情况,您放弃了 GIL 瓶颈,以换取极其缓慢的 I/O 瓶颈。

简而言之,您已经正确理解了 I/O 操作释放 GIL,您只是得出了关于并行文件读取的错误结论。