什么是Python线程

46 投票
4 回答
12464 浏览
提问于 2025-04-17 08:57

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

  1. Python线程是Python自己实现的,还是操作系统实现的?
  2. 当我使用htop查看时,一个多线程的脚本会有多个条目——它们的内存使用量相同,命令也相同,但PID(进程ID)不同。这是否意味着一个[Python]线程实际上是一种特殊的进程?(我知道在htop中有一个设置可以将这些线程显示为一个进程——Hide userland threads
  3. 文档中提到:

线程可以被标记为“守护线程”。这个标记的意义在于,当只剩下守护线程时,整个Python程序会退出。

我的理解是:当所有非守护线程结束时,主线程也会终止。

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

4 个回答

5
  1. Python中的线程实际上是由解释器来实现的,因为有一个叫做全局解释器锁(GIL)的东西。虽然它在技术上是使用操作系统级别的线程机制,但GIL的存在让它在应用层的线程模型上变得有些混乱。在类Unix系统上,它使用的是pthreads,但由于GIL的影响,你在ps/top命令的输出中会看到多个线程,但它的表现(在性能上)就像是软件实现的线程。

  2. 不,你看到的只是操作系统底层线程实现的表现。这种行为是类Unix系统的pthreads线程模型所导致的,听说Windows也是用类似的方式来实现线程的。

  3. 当你的程序关闭时,它会等待所有线程完成。如果有些线程可能会无限期地延迟退出,最好把这些线程标记为“守护线程”,这样即使这些线程还在运行,你的程序也能正常结束。

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

16

我对这个实现不是很熟悉,所以我们来做个实验:

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 中有其他线程阻止程序终止,或者守护线程不被支持。

注意:我使用的是 Ubuntu 11.10,python 2.7.2+ 和 jython 2.2.1,运行在 java1.6.0_23 上。

41
  1. 在我知道的所有Python实现中(包括C Python、PyPy和Jython),Python的线程都是通过操作系统的线程来实现的。每个Python线程背后都有一个操作系统线程。

  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程序在只剩下守护线程时退出”,那么Python的守护线程就不算是Python程序的一部分吗?

你的理解是不正确的。对于操作系统来说,一个进程是由许多线程组成的,所有线程都是平等的(对于操作系统来说,主线程并没有什么特别之处,除了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行,直到被杀掉。

撰写回答