【问题标题】:Why are threads spread between CPUs?为什么线程分布在 CPU 之间?
【发布时间】:2016-05-16 06:50:16
【问题描述】:

我正试图了解线程与 CPU 使用率。有很多关于线程与多处理的讨论(一个很好的概述是 this answer)所以我决定通过在运行 Windows 10、Python 3.4 的 8 CPU 笔记本电脑上启动最大数量的线程来测试这一点。

我的假设是所有线程都绑定到一个 CPU。

编辑:事实证明这不是一个好的假设。我现在明白,对于多线程代码,一次只能运行 一个 段 python 代码(无论在哪里/在哪个核心上)。这对于多处理代码(其中进程是独立的并且确实独立运行)是不同的。
虽然我读到了这些差异,但实际上是 one answer 澄清了这一点。

我认为它也解释了下面的 CPU 视图:它是分布在许多 CPU 上的许多线程的平均视图,但在给定时间只有一个线程在运行(这“平均”到所有线程都运行时间)。

它不是链接问题的重复(它解决了相反的问题,即一个核心上的所有线程),我将把它挂起来,以防有一天有人有类似的问题,希望我的启发有所帮助。


代码

import threading
import time


def calc():
    time.sleep(5)
    while True:
        a = 2356^36

n = 0
while True:
    try:
        n += 1
        t = threading.Thread(target=calc)
        t.start()
    except RuntimeError:
        print("max threads: {n}".format(n=n))
        break
    else:
        print('.')

time.sleep(100000)

导致启动了 889 个线程。

然而,CPU 上的负载是分散的(对于纯 CPU 计算来说非常低,否则笔记本电脑在不运行我的脚本时会因为空负载而空闲):

为什么会这样?线程是否在 CPU 之间作为一个包不断移动,而我看到的只是一个平均值(现实是在给定时刻所有线程都在一个 CPU 上)?还是它们确实是分布式的?

【问题讨论】:

  • 您为什么希望将线程整合到一个内核中?线程的目的是同时执行多个运行中的代码,单个内核一次不能做超过一件事。将它们展开是真正获得并行性的唯一方法。
  • @JarrodRoberson:我的情况实际上与您所链接的问题相反。但它间接地帮助我理解了关键部分:使用多线程,一次只能运行一段 python 代码(无论它在哪个内核上)。这不是多处理的情况(其中进程是独立的,因此不需要 GIL)
  • 这是 Python 关于线程的特定限制。这与使用其他语言不同。正如 Rolf Shorpion 所提到的,请参阅 Python 文档中的“全局解释器锁”。

标签: python multithreading


【解决方案1】:

嗯。多线程的重点是确保它们的工作得到分散。一个非常简单的作弊方法是使用与 CPU 内核一样多的线程。关键是它们都是独立的,因此它们实际上可以同时运行。如果它们在同一个核心上,一次只能真正运行一个线程。他们会来回传递该内核以在操作系统级别进行处理。

你的假设是错误的和奇怪的。是什么让你认为它们应该在同一个 CPU 上运行并因此以 1/8 的速度运行?因为线程化它们的唯一原因通常是让整个批次比单独的单个内核更快。

事实上,如果不是同时在多个内核上独立运行,您认为编写并行代码到底是为了什么?像这样既没有意义又很难做到,让我们制作复杂的获取、分支和分叉例程来完成事情,比一个核心仅仅插入数据要慢吗?

【讨论】:

  • 关键是它们都是独立的,所以它们实际上可以同时运行。不,它们不能。现在我红色了一些不那么兴奋的答案,我明白在多线程的情况下,只有一段 python 代码可以在给定的时刻运行(由于 GIL),它们只是在它们之间切换以给人一种并发的印象。所以没有真正的并发。多处理(如“许多独立进程”)确实允许并发运行(因为进程是独立的 - 这不是线程的情况)。
  • 这个答案是 - 重用这个短语 - “错误和离奇”。也许坚持用你知道的语言回答问题?
【解决方案2】:

直到今天,“一个线程持有 GIL”的情况仍然存在。所以一次运行一个线程。

线程在操作系统级别进行管理。发生的情况是每 100 个“滴答”(=解释器指令)运行的线程释放 GIL 并重置滴答计数器。

由于本例中的线程进行连续计算,因此非常快地达到了 100 条指令的滴答限制,导致 GIL 几乎立即释放,线程之间的“战斗”开始获取 GIL。

因此,我的假设是您的操作系统的负载高于预期,因为(太)快的线程切换 + 几乎连续释放和获取 GIL。操作系统花在切换上的时间比实际进行任何有用的计算要多。

正如您自己提到的,对于一次使用多个内核,最好查看多处理模块(joblib/Parallel)。

有趣的阅读: http://www.dabeaz.com/python/UnderstandingGIL.pdf

【讨论】:

    猜你喜欢
    • 2019-04-08
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    • 2013-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多