You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python非Daemon线程调用栈持续运行问题排查

非守护线程持续运行导致进程无法终止的原因分析

问题场景

我编写了一段用队列管理任务的多线程图片下载代码,调试时确认队列已为空,my_queue.join()执行后主线程正常输出“All done!”,但非Daemon线程仍在调用栈中持续运行,进程无法结束。将线程设置为threading.Thread(target=worker, args=(my_queue,), daemon=True).start()后,进程可正常终止,想知道非守护线程持续运行的原因。

代码如下:

import queue
from urllib.request import urlopen, Request
import os
import threading


HEADER = {'User-Agent': 'Mozilla/5.0'}

# Note: only URL:s ending on .jpg works
IMG_URLS = [
            "https://cdn.pixabay.com/photo/2017/03/08/13/06/dog-2126708__340.jpg",
            "https://cdn.pixabay.com/photo/2016/06/05/22/45/otter-1438381__340.jpg",
            "https://cdn.pixabay.com/photo/2014/10/22/14/43/otter-498046__340.jpg",
            "https://cdn.pixabay.com/photo/2017/10/23/23/43/otter-2883047__340.jpg"
            ]

# Change to desired folder
SAVE_FOLDER = r"C:\Users\xxxxxxxx"  
my_queue = queue.Queue()

def generate_queue(list):
    for url in list:
        my_queue.put(url)


def create_and_run_threads():
    number_threads=2
    for _ in range(number_threads):
        threading.Thread(target=worker, args=(my_queue,)).start()

def worker(q):
    while q:
        url=q.get()
        download_img(url)
        my_queue.task_done()


def download_img(url):
    img_bytes = request_and_open(url)
    img_name = url.split("/")[-1]
    file_path = os.path.join(SAVE_FOLDER, img_name)

    with open(file_path, "wb") as img:
        img.write(img_bytes)
        print(f"Downloading image: {img_name}")

def request_and_open(URL):
    request = Request(URL, headers=HEADER)
    url_info_byte = urlopen(request, timeout=20).read()
    return url_info_byte


def main():
    generate_queue(IMG_URLS)
    create_and_run_threads()
    my_queue.join()
    print("All done!")

    

if __name__ == "__main__":
    main()

原因分析

问题核心出在worker函数的循环条件和队列的阻塞特性上:

  • 循环条件while q永远为True:这里的q是队列实例对象,只要这个对象没有被销毁,判断结果就一直是真,线程不会主动退出循环。
  • q.get()默认是阻塞式调用:当队列中的所有任务都处理完毕后,线程会卡在url=q.get()这一行,一直等待新的任务被放入队列,不会自己停止。

主线程执行my_queue.join()只是确认所有已提交的任务都处理完成,但不会通知工作线程停止等待。此时非守护线程还在阻塞等待队列的新任务,而Python进程会等待所有非守护线程结束后才会终止,所以进程无法正常退出。

设置daemon=True后,主线程结束时会强制终止所有守护线程,进程也就随之正常结束了。

修复方案

可以通过两种方式让工作线程主动退出:

  1. 给q.get()设置超时,捕获空队列异常后退出:
def worker(q):
    while True:
        try:
            # 设置超时时间,避免永久阻塞
            url = q.get(timeout=1)
            download_img(url)
            q.task_done()
        except queue.Empty:
            # 队列为空且超时,退出循环
            break
  1. 主线程在任务完成后发送终止标记,让线程主动退出:
def main():
    generate_queue(IMG_URLS)
    number_threads = 2
    create_and_run_threads()
    my_queue.join()
    # 给每个工作线程发送终止标记
    for _ in range(number_threads):
        my_queue.put(None)
    print("All done!")

def worker(q):
    while True:
        url = q.get()
        if url is None:
            # 收到终止标记,退出线程
            q.task_done()
            break
        download_img(url)
        q.task_done()

内容的提问来源于stack exchange,提问作者EstherPM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:45:01