searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

python线程的daemon属性

2023-06-08 08:41:25
5
0

事情从一个奇怪的故障说起,一个python写的systemd服务,运行过程中发生故障抛出了异常,本来期望这个不处理的异常能导致进程退出,然后由systemd重新拉起服务修复。实际情况却是,异常抛出了,进程仍然在运行,导致故障没有自愈。

由于服务程序有一个比较复杂的框架,一开始怀疑这个应用程序框架捕获了异常导致进程继续运行。快速搜索了一遍异常处理的代码,发现抛出异常的地方不会再被捕获异常,事情就显得比较诡异了,抛出异常退出的功能运行了很久了。

抓了各线程栈,发现一个线程栈顶是在获取线程锁。读了一下代码,是threading模块在线程退出时等一串锁。代码细节稍微有点复杂,但是栈顶函数的注释很清楚:Wait until the Python thread state of all non-daemon threads get deleted.就是在等非daemon的线程退出。

排查了所有线程及其子类,发现一处新增的从threading.Timer派生的定时任务,并未设置daemon属性。按照threading模块的说明,子线程线程默认继承daemon属性,API文档中也并未说明其daemon属性有特殊处理,简单验证一下:

也就是Timer线程默认是non-daemon线程,因此主线程退出是,会等Timer线程退出,即使主线程抛出了异常也会等Timer线程,而Timer线程的执行函数是一个无限循环,不会退出。

简化代码如下:

#! /bin/python3

import sys
import datetime
import threading
import time


def HeartBeat():
    print(f"heart-beat start, thread name {threading.current_thread().name}, "
          f"id {threading.current_thread().native_id}")
    while True:
        print(f"[{datetime.datetime.now()}] "
              f"{threading.current_thread().name} "
              "heart-beat")
        time.sleep(1.0)


def main():
    main_thread = threading.main_thread()
    print(f"main thread name {main_thread.name} id {main_thread.native_id}")
    beater = threading.Timer(1.0, function=HeartBeat)
    # beater.setDaemon(True)
    print(f"[{datetime.datetime.now()}] timer thread {beater.name} start.")
    beater.start()

    time.sleep(5.0)
    print(f"[{datetime.datetime.now()}] main thread exit.")


if __name__ == '__main__':
    sys.exit(main())
    print("ok")

执行情况如下,可以看到主线程退出后,子线程还一直执行,进程未能退出。

抓取进程栈如下:

将子线程daemon设置为False, 执行情况如下,可见主线程退出后,进程顺利退出。

总结threading线程的daemon机制:

daemon:

A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.

The entire Python program exits when no alive non-daemon threads are left.

  1. 线程的daemon属性必须在start()之前调用,否则会抛出RuntimeError异常。
  2. 初始值继承自创建线程。
  3. 主线程不是daemon线程,因此主线程创建的线程默认都是non-daemon线程。
  4. 只有当所有的non-daemon线程都退出后,Python程序(进程)才会退出。
0条评论
作者已关闭评论
王****龙
2文章数
0粉丝数
王****龙
2 文章 | 0 粉丝
王****龙
2文章数
0粉丝数
王****龙
2 文章 | 0 粉丝
原创

python线程的daemon属性

2023-06-08 08:41:25
5
0

事情从一个奇怪的故障说起,一个python写的systemd服务,运行过程中发生故障抛出了异常,本来期望这个不处理的异常能导致进程退出,然后由systemd重新拉起服务修复。实际情况却是,异常抛出了,进程仍然在运行,导致故障没有自愈。

由于服务程序有一个比较复杂的框架,一开始怀疑这个应用程序框架捕获了异常导致进程继续运行。快速搜索了一遍异常处理的代码,发现抛出异常的地方不会再被捕获异常,事情就显得比较诡异了,抛出异常退出的功能运行了很久了。

抓了各线程栈,发现一个线程栈顶是在获取线程锁。读了一下代码,是threading模块在线程退出时等一串锁。代码细节稍微有点复杂,但是栈顶函数的注释很清楚:Wait until the Python thread state of all non-daemon threads get deleted.就是在等非daemon的线程退出。

排查了所有线程及其子类,发现一处新增的从threading.Timer派生的定时任务,并未设置daemon属性。按照threading模块的说明,子线程线程默认继承daemon属性,API文档中也并未说明其daemon属性有特殊处理,简单验证一下:

也就是Timer线程默认是non-daemon线程,因此主线程退出是,会等Timer线程退出,即使主线程抛出了异常也会等Timer线程,而Timer线程的执行函数是一个无限循环,不会退出。

简化代码如下:

#! /bin/python3

import sys
import datetime
import threading
import time


def HeartBeat():
    print(f"heart-beat start, thread name {threading.current_thread().name}, "
          f"id {threading.current_thread().native_id}")
    while True:
        print(f"[{datetime.datetime.now()}] "
              f"{threading.current_thread().name} "
              "heart-beat")
        time.sleep(1.0)


def main():
    main_thread = threading.main_thread()
    print(f"main thread name {main_thread.name} id {main_thread.native_id}")
    beater = threading.Timer(1.0, function=HeartBeat)
    # beater.setDaemon(True)
    print(f"[{datetime.datetime.now()}] timer thread {beater.name} start.")
    beater.start()

    time.sleep(5.0)
    print(f"[{datetime.datetime.now()}] main thread exit.")


if __name__ == '__main__':
    sys.exit(main())
    print("ok")

执行情况如下,可以看到主线程退出后,子线程还一直执行,进程未能退出。

抓取进程栈如下:

将子线程daemon设置为False, 执行情况如下,可见主线程退出后,进程顺利退出。

总结threading线程的daemon机制:

daemon:

A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.

The entire Python program exits when no alive non-daemon threads are left.

  1. 线程的daemon属性必须在start()之前调用,否则会抛出RuntimeError异常。
  2. 初始值继承自创建线程。
  3. 主线程不是daemon线程,因此主线程创建的线程默认都是non-daemon线程。
  4. 只有当所有的non-daemon线程都退出后,Python程序(进程)才会退出。
文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0