为什么多线程和单线程没有执行时间差异

Why is there no execution time difference between multithreading and singlethreading

我正在努力学习 python 语言及其概念。我写了一些代码来玩多线程。但是我注意到多线程和单线程之间没有执行时间差异。

要运行脚本的机器有4台core/thread。

 def get_tokens(file_name,map):
    print(file_name)
    counter = 0
    with open(file_name,'r',encoding='utf-8-sig') as f:
        for line in f:
            item = json.loads(line,encoding='utf-8')
            if 'spot' in item and item['sid'] == 4663:
                counter+=1
                if counter == 500:
                    break
                tokens = nltk.word_tokenize(item['spot'],language='english')
                for token in tokens:
                    if token not in map:
                        map[token] = 1
                    else:
                        map[token] = map[token] + 1;

 start_time = time.time()
 map = dict();
 with ThreadPoolExecutor(max_workers=3) as executor:
    for file in FileProcessing.get_files_in_directory('D:\Raw Data'):
        future = executor.submit(FileProcessing.get_tokens, file, map)

 end_time = time.time()
 print("Elapsed time was %g seconds" % (end_time - start_time))

原始数据中每个文件的大小都大于 25 MB。所以我认为它们之间一定是有区别的。但是没有。为什么 ?我是不是在代码或多线程概念上犯了错误?

问题是能否通过线程改进代码。如果代码是连续的,并且一件事情接连发生在一条直线上,那么无论您有多少线程,代码都将 运行 相同。但是,如果代码可以分支并同时执行操作 a 和操作 b,那么它将执行。仔细查看您的代码,似乎没有分支,至少我不知道。

Python 有 Global Interpretor Lock (GIL), that prevents two threads of execution in the same python process from executing at the same time. Therefore, while python threads give you multiple control paths within a single process those multiple control paths cannot execute simultaneous on a multi-core machine. An alternative is to use the python multiprocessing framework which will actually create separate processes and then have the processes communicate via inter-process communication (IPC). You can also try using the ProcessPoolExecutor 会产生多个进程,因此你不会有 GIL

的问题

CPython(Python 的标准实现)不支持不同 CPU 上的多线程。所以你确实可以有多个线程,但它们都将 运行 在同一个 CPU 上,并且你不会有 CPU 绑定进程的速度改进(你会有 I/O绑定进程)。

原因是臭名昭著的 GIL(全局解释器锁)。 Python的核心不是线程安全的,因为它进行垃圾回收的方式,所以它使用了锁,这意味着线程一个接一个地访问python个对象运行。

在您的特定情况下,您正在做一些 I/O 和一些处理。在 python 中进行多处理时会产生显着的开销,这无法通过 I/O 中速度的提高来补偿(与处理文件的时间相比,读取文件的时间可能很小)。

如果您需要进行真正的多线程处理,请查看 Cython(不要与 CPython 混淆)和 "no_gil",或 c-extensions,或多处理模块。

您的代码是 CPU 绑定的 - 默认情况下,多线程将保持在单个 CPU 中。您可以使用 multiprocessing package,它允许您创建子流程并基本上绕过 GIL(全局解释器锁)。当然,这只有在您有多个 CPU 时才有用。而且我认为这个用例不需要多个进程的开销。

GIL 注释是正确的,但此代码更可能是 IO 绑定而不是 CPU 绑定。

即使 如果你使用像 C 或 Java 这样的东西,你仍然在通过串行接口读取文件,所以除非你不能处理 100-300 MB/s 个 JSON,您不会从线程中看到性能优势。

@DevShark 确实说过您会看到 IO 绑定进程的好处,但它比这更复杂。对于高延迟的并发网络连接来说,这往往更多。在这种情况下,您将受 IO 限制 在磁盘 ,而不是进程(您不是在等待远程响应),因此并行性无济于事。

如果您是 CPU-bound,有真正的线程,并且正在使用旋转磁盘,您仍然需要仔细调整缓冲区大小。 10 毫秒的寻道时间可能会要了你的命,所以如果你想要高磁盘吞吐量,你需要使用缓冲区大小比这大得多的缓冲读取。对于 100MB/s 的磁盘,寻道时间为 10 毫秒,我会使用 10MB 缓冲区,但这仍然意味着您要花费 10% 的时间在磁盘上寻道。我还会协调我的阅读,所以一次只有一个 reader 阅读。