跟踪 python 中的悬空线程

Tracking dangling threads in python

我有一个基于 python 3.7.2 asyncio 的应用程序。有一个端点公开了一些线程信息:

threads_info = {}
for thread in enumerate():
    threads_info[thread.__str__()] = traceback.format_stack(sys._current_frames()[thread.ident])

据我所知,除主线程外不应该有其他线程 运行ning,但是当我查询端点时,我看到了这个奇怪的 ThreadPoolExecutor。它从一个工人开始,并不断增加:

知道这个 ThreadPoolExecutor 为什么、如何以及是什么吗?也许有一些方法可以查看它是在代码中的哪个位置创建的,或者是哪个包创建的?

我用于 运行 我的应用程序的 Dockerfile:

FROM python:3.7.2-alpine as base

FROM base as builder
RUN mkdir /install
WORKDIR /install
COPY requirements /requirements
RUN apk add \
    "gcc>8.2.0" \
    "g++>8.2.0" \
    "libffi-dev>3.2.1" \
    "musl-dev>1.1.20"
RUN pip install --install-option="--prefix=/install" -r /requirements

FROM base
RUN apk add --no-cache procps
COPY --from=builder /install /usr/local
COPY src /app
WORKDIR /app
RUN mkdir logs
ENTRYPOINT ["python", "-u", "app.py"]
EXPOSE 80/tcp

我的需求文件:

quart==0.8.1
aiohttp==3.5.4
cchardet==2.1.4
aiodns==1.2.0
requests==2.21.0
psutil==5.6.1

Any ideas why, how and what is this ThreadPoolExecutor?

ThreadPoolExecutor is the thread pool implementation provided by the concurrent.futures 模块。它通过将同步代码交给单独的线程来异步执行同步代码。池的目的是避免为每个单独的任务创建和加入线程的延迟;相反,池只创建工作线程一次,并将其保存在池中供以后使用。池中的最大线程数可以配置,默认为核心数乘以5。

您在代码中看到的线程属于 ThreadPoolExecutor,由您正在使用的库之一实例化。具体来说,asyncio 创建一个执行器供 run_in_executor 方法使用。 asyncio 本身使用此执行器为本机没有异步接口的调用提供异步接口,例如 OS-提供的 DNS 解析。

一般来说,当使用非平凡的第三方库时,您不能假设您的代码将是唯一创建线程的代码。当迭代活动线程时,您只需忽略那些您没有创建的线程,这可以通过在 Thread 对象上使用自定义属性标记您创建的线程来实现。

Perhaps there is some way to see where in the code is it created or which package creates it?

是的,正如之前的回答所述,它是 asyncio 默认执行程序。 为了调试哪个包是我必须编写自己的执行程序的罪魁祸首:

class AsyncioDefaultExecutor(ThreadPoolExecutor):

    def __init__(self, thread_name_prefix='', max_workers=None):
        self.logger = get_logger("asyncioTh")
        super(AsyncioDefaultExecutor, self).__init__(thread_name_prefix=thread_name_prefix)

    def submit(self, fn, *args, **kwargs):
        debug_info = "Function " + fn.__name__ + " in " + fn.__code__.co_filename + ":" + \
                     str(fn.__code__.co_firstlineno) + "\n" + "".join(traceback.format_stack())
        self.logger.info(debug_info)
        return super(AsyncioDefaultExecutor, self).submit(fn, *args, **kwargs)

并将其设置为默认执行器:

loop.set_default_executor(AsyncioDefaultExecutor())

这导致每次提交新任务时都有很好的回溯。