关于多线程:什么是python线程

What is a python thread

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

  • python线程是python还是os实现?
  • 当我使用htop时,多线程脚本有多个条目——相同的内存消耗、相同的命令但不同的PID。这是否意味着线程实际上是一种特殊的进程?(我知道htop中有一个设置,将这些线程显示为一个进程-Hide userland threads)
  • 文件上说:
  • A thread can be flagged as a"daemon thread". The significance of this
    flag is that the entire Python program exits when only daemon threads
    are left.

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

    所以,如果"只剩下守护进程线程,整个python程序就退出了",那么python守护进程线程就不是python程序的一部分了?


  • 在我知道的所有实现(c python、pypy和jython)中,python线程都是使用操作系统线程实现的。对于每个python线程,都有一个底层的OS线程。

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

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

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

    此功能的代码为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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函数。

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

    My interpretation/understanding was: main thread terminates when all
    non-daemon threads are terminated.

    So python daemon threads are not part of python program if"the entire
    Python program exits when only daemon threads are left"?

    你的理解是错误的。对于操作系统,进程由许多线程组成,所有线程都是相等的(操作系统的主线程没有什么特别的,除了C运行时在main函数的末尾添加对_exit的调用)。操作系统不知道守护进程线程。这纯粹是一个Python概念。

    python解释器使用本机线程来实现python线程,但必须记住创建的线程列表。并且使用它的atexit钩子,它确保只有在最后一个非守护进程线程终止时,_exit函数才返回到操作系统。当使用"整个python程序"时,文档指的是整个过程。

    下面的程序可以帮助理解守护进程线程和常规线程之间的区别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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:])

    如果您使用"--使用守护进程"执行这个程序,您将看到该程序只会打印少量的Working hard行。如果没有这个标志,即使主线程完成,程序也不会终止,并且程序将打印Working hard行,直到终止为止。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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()
  • 使用ps -o cmd,nlwp 报告的线程数是NUM_THREADS+1(主线程多报告一个),只要OS工具检测到线程数,就应该是OS线程。我尝试了cpython和jython,尽管jython中有一些其他线程正在运行,但是对于我添加的每个额外线程,ps会将线程计数增加一个。

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

  • 我在开始线程之前添加了以下行:

    1
    thread.daemon = True

    当我执行使用cpython时,程序几乎立即终止,并且没有使用ps发现进程,所以我猜程序是与线程一起终止的。在Jython中,程序以相同的方式工作(它没有终止),所以可能还有一些来自JVM的线程阻止程序终止,或者不支持守护进程线程。

  • 注意:我在java1.6.0.u 23上使用了Ubuntu11.10和python 2.7.2+以及jython2.2.1。


  • python线程实际上是一个解释器实现,因为所谓的全局解释器锁(gil),即使它在技术上使用操作系统级线程机制。在*nix上,它使用pthreads,但是gil有效地使它成为应用程序级线程范例的混合体。因此,您将在*nix系统上以ps/top输出多次看到它,但它的行为(性能方面)仍然类似于软件实现的线程。

  • 不,您只是看到了操作系统的底层线程实现。这种行为是由*nix pthread-like线程暴露的,或者我告诉Windows也这样实现线程。

  • 当程序关闭时,它也会等待所有线程完成。如果您有线程,这可能会延迟退出,明智的做法是将这些线程标记为"守护进程",并允许您的程序完成,即使这些线程仍在运行。

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

    • Linux公报:了解Python中的线程。
    • DougHellman:Python中的多处理技术
    • David Beazley:Pycon 2010:了解python gil(视频演示)


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

    "main thread terminates when all non-daemon threads are terminated."

    So python daemon threads are not part of Python program if"the entire Python program exits when only daemon threads are left"?

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

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

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