【问题标题】:What is a python thread什么是python线程
【发布时间】:2011-12-24 08:37:47
【问题描述】:

我有几个关于 Python 线程的问题。

  1. Python 线程是 Python 还是 OS 实现?
  2. 当我使用 htop 时,多线程脚本有多个条目 - 相同的内存消耗、相同的命令但不同的 PID。这是否意味着 [Python] 线程实际上是一种特殊的进程? (我知道 htop 中有一个设置可以将这些线程显示为一个进程 - Hide userland threads
  3. Documentation 说:

可以将线程标记为“守护线程”。这件事的意义 flag 是当只有守护线程时整个 Python 程序退出 剩下的。

我的解释/理解是:当所有非守护线程都终止时,主线程终止。

如果“当只剩下守护线程时整个 Python 程序退出”,那么 Python 守护线程就不是 Python 程序的一部分?

【问题讨论】:

  • 你的意思是threading.Thread对吗?
  • 是的,我愿意。标准 Python 中还有其他线程吗?
  • 是的,thread 模块为本地线程提供了另一个接口(但他们使用相同的本地实现)。

标签: python multithreading


【解决方案1】:
  1. 在我知道的所有实现(C Python、PyPy 和 Jython)中,Python 线程都是使用操作系统线程实现的。对于每个 Python 线程,都有一个底层 OS 线程。

  2. 某些操作系统(Linux 就是其中之一)在所有正在运行的进程列表中显示由同一可执行文件启动的所有不同线程。这是操作系统的实现细节,而不是 Python 的实现细节。在其他一些操作系统上,列出所有进程时您可能看不到这些线程。

  3. 当最后一个非守护线程完成时,进程将终止。此时,所有守护线程都将被终止。因此,这些线程是您进程的一部分,但不会阻止它终止(而常规线程会阻止它)。这是用纯 Python 实现的。当调用系统_exit 函数时进程终止(它将杀死所有线程),并且当主线程终止(或调用sys.exit)时,Python 解释器检查是否有另一个非守护线程正在运行。如果没有,则调用_exit,否则等待非守护线程完成。


守护线程标志由threading 模块在纯Python 中实现。加载模块时,会创建一个Thread对象来表示主线程,并将其_exitfunc方法注册为atexit钩子。

这个函数的代码是:

class _MainThread(Thread):

    def _exitfunc(self):
        self._Thread__stop()
        t = _pickSomeNonDaemonThread()
        if t:
            if __debug__:
                self._note("%s: waiting for other threads", self)
        while t:
            t.join()
            t = _pickSomeNonDaemonThread()
        if __debug__:
            self._note("%s: exiting", self)
        self._Thread__delete()

sys.exit 被调用或主线程终止时,Python 解释器将调用该函数。当函数返回时,解释器会调用系统_exit函数。当只有守护线程在运行(如果有)时,函数将终止。

_exit函数被调用时,操作系统会终止所有进程线程,然后终止进程。在所有非守护线程完成之前,Python 运行时不会调用_exit 函数。

所有线程都是进程的一部分。


我的解释/理解是:主线程在所有 非守护线程被终止。

所以如果“整个 只剩下守护线程时Python程序退出”?

你的理解不正确。对于OS来说,一个进程是由很多线程组成的,所有线程都是相等的(对于OS来说,主线程没什么特别的,只是C运行时在main的末尾添加了对_exit的调用功能)。并且操作系统不知道守护线程。这纯粹是一个 Python 概念。

Python 解释器使用原生线程来实现 Python 线程,但必须记住创建的线程列表。并且使用它的atexit 钩子,它确保_exit 函数仅在最后一个非守护线程终止时返回到操作系统。使用“整个 Python 程序”时,文档指的是整个过程。


下面的程序可以帮助理解守护线程和普通线程的区别:

import sys
import time
import threading

class WorkerThread(threading.Thread):

    def run(self):
        while True:
            print 'Working hard'
            time.sleep(0.5)

def main(args):
    use_daemon = False
    for arg in args:
        if arg == '--use_daemon':
            use_daemon = True
    worker = WorkerThread()
    worker.setDaemon(use_daemon)
    worker.start()
    time.sleep(1)
    sys.exit(0)

if __name__ == '__main__':
    main(sys.argv[1:])

如果您使用“--use_daemon”执行此程序,您将看到该程序将仅打印少量Working hard 行。如果没有这个标志,即使主线程结束,程序也不会终止,程序将打印Working hard行,直到它被杀死。

【讨论】:

  • >当最后一个非守护线程完成时,进程将终止。 这个标志的意义在于,当只有守护线程时整个 Python 程序退出留下了。
  • 更新了我的答案以解释如何在 Python 中实现守护线程。
  • 感谢您的耐心等待。 > 当_exit函数被调用时,操作系统会终止所有进程线程,然后终止进程。 _exitfunc被调用后所有线程,包括守护进程,都会被操作系统终止?这不是我所看到的 - 主线程终止后守护线程仍在运行。我的问题之一是:如何说 Python 整个 程序(进程)退出仍有线程(守护进程)
  • 感谢您的更新(尽管我没有收到有关它的通知。我想现在我终于明白了。对我来说主要的困惑是“守护进程”名称,这是从其他上下文中告诉我的某些东西正在分离运行(例如在后台运行的守护进程 python 脚本中)。我认为守护线程正在与其他线程“分离”运行,并在非守护线程退出时继续运行。
  • 从您的回答和示例中,我会这样说: > 线程可以被标记为“守护线程”。这个标志的意义在于,当只剩下守护线程时,整个 Python 程序退出(即强制杀死守护线程)。
【解决方案2】:

我不熟悉实现,所以我们做一个实验:

import threading
import time

def target():
    while True:
        print 'Thread working...'
        time.sleep(5)

NUM_THREADS = 5

for i in range(NUM_THREADS):
    thread = threading.Thread(target=target)
    thread.start()
  1. 使用ps -o cmd,nlwp <pid>报告的线程数是NUM_THREADS+1(主线程多一个),所以只要操作系统工具检测到线程数,就应该是操作系统线程。我尝试使用 cpython 和 jython,尽管在 jython 中还有一些其他线程正在运行,但对于我添加的每个额外线程,ps 将线程计数增加一。

  2. 我不确定htop 的行为,但ps 似乎是一致的。

  3. 我在启动线程之前添加了以下行:

    thread.daemon = True
    

    当我使用 cpython 执行时,程序几乎立即终止,使用ps 没有找到任何进程,所以我的猜测是程序与线程一起终止。在 jython 中,程序以相同的方式工作(它没有终止),因此 jvm 中可能有一些其他线程阻止程序终止或不支持守护线程。

注意:我在 java1.6.0_23 上使用 Ubuntu 11.10 和 python 2.7.2+ 和 jython 2.2.1

【讨论】:

    【解决方案3】:
    1. Python 线程实际上是一种解释器实现,因为所谓的全局解释器锁 (GIL),即使它在技术上使用的是操作系统级别的线程机制。在 *nix 上,它使用 pthreads,但 GIL 有效地使其成为一个坚持应用程序级线程范式的混合体。因此,您将在 *nix 系统上多次在 ps/top 输出中看到它,但它的行为(性能方面)仍然像软件实现的线程。

    2. 不,您只是看到了操作系统的底层线程实现。这种行为是由 *nix pthread-like 线程暴露的,或者我告诉我,甚至 Windows 确实以这种方式实现线程。

    3. 当您的程序关闭时,它也会等待所有线程完成。如果您有线程,可能会无限期地推迟退出,那么将这些线程标记为“守护程序”并允许您的程序完成可能是明智的,即使这些线程仍在运行。

    一些您可能感兴趣的参考资料:

    【讨论】:

    • 1.这是错误的。 Python 线程是操作系统线程。 GIL 确实会影响多核系统的性能,但在某些情况下,您仍然可以从多核中获得一些好处(特别是当线程将大部分时间用于 cpu 绑定在 C 库函数中时)。
    • 我们可以就此争论。事实是:(c)Python 只是出于方便而使用底层线程机制(为什么要重新实现一些东西,这已经证明很好了?),但否认了“真正的”操作系统线程的好处。所以a)是的,它们在技术上是一个操作系统级线程,但是b)不,它们不是一个操作系统级线程,因为除了操作系统级线程的共享内存之外,您没有任何好处。即便如此,如果您想提高性能,我还是建议不要使用它们。
    • 曾经有一次谈话表明,如果启动多个线程(在某些罕见的情况下甚至使用外部函数),它甚至可能会大大减慢您的速度,因为上下文切换的附加层.像鸭子一样走路、游泳和嘎嘎叫,即使它不是天生的鸭子! ;-)
    • 我同意。在这一点上,由于我对 GIL 和线程的不满,我可能开始更频繁地使用多处理模块,因为它绕过了 GIL 的限制。我建议任何正在考虑使用多线程以提高性能的人。
    • @DonQuestion 是的,使用太多线程会导致上下文切换和对锁的需求会减慢您的应用程序的速度。对于任何线程系统和任何编程语言都是如此,这不是 Python 独有的。
    【解决方案4】:

    这个问题有很好的答案,但我觉得守护线程问题仍然没有以简单的方式解释。所以这个答案只针对第三个问题

    “当所有非守护线程都终止时,主线程终止。”

    如果“当只剩下守护线程时整个 Python 程序退出”,那么 Python 守护线程就不是 Python 程序的一部分?

    如果您考虑守护程序是什么,它通常是一个服务。一些在无限循环中运行的代码,服务于请求、填充队列、接受连接等。其他线程使用它。它在单独运行时没有任何目的(在单个进程中)。

    所以程序不能等待守护线程终止,因为它可能永远不会发生。当所有非守护线程完成后,Python 将结束程序。它还会停止守护线程

    要等到守护线程完成其工作,请使用join() 方法。 daemon_thread.join() 将使 Python 在退出之前也等待守护线程。 join() 也接受 timeout 参数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-14
      • 2013-02-11
      • 2015-02-03
      • 2010-11-14
      • 2021-09-09
      • 2022-11-01
      • 1970-01-01
      • 2021-12-09
      相关资源
      最近更新 更多