为什么我在 Python 中得到一个额外的线程 运行?

Why am I getting an extra thread running in Python?

我正在使用 Raspberry Pi。

我有一个按钮连接到一个 GPIO 引脚,一个 LED 连接到另一个引脚。当按钮被按下时,一个函数被调用。 当该功能处于活动状态时,我希望 LED 闪烁,这需要后台线程。这实质上意味着当我的按钮处理程序处于 运行 时,我需要一个后台线程 运行,并在我的按钮处理程序停止时停止。

运行以下代码证明了我遇到的问题。代码以单个线程开始,但是当我按下按钮时,threading.active_count() 显示有 3 个线程 运行ning(不是预期的 2 个)。当我的线程完成 运行ning 时,我剩下 2 个后台线程 - 而不是预期的 1 个。

这是我的代码:

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import time
import threading
from threading import Thread, Event

#########################
# Function to Blink LED #
#########################

# Sample function that blinks the LED
def blink_led_func(led, stop_blinking):
    while not stop_blinking.is_set():
        print("Blinking LED...")
        time.sleep(0.5)

#############
# Decorator #
#############

# Starts a background thread which blinks the LED, runs the decorated
# function, and when the function is done running, stops blinking the LED
class blink_led:
    def __init__(self, function):
        self.f = function

    def __call__(self, channel):
        stop = Event()
        t = Thread(target=blink_led_func, args=(1, stop))
        t.start()

        self.f(channel)

        stop.set()
        t.join()

##################
# Button Handler #
##################

# Called when button is pressed
@blink_led
def btn_handler(channel):
    print("Button pressed")
    time.sleep(5)

##############
# Setup GPIO #
##############

# Setup pin
GPIO.setmode(GPIO.BOARD)
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_UP)

##############################
# Add Button Event Listeners #
##############################

GPIO.add_event_detect(12, GPIO.FALLING, callback=btn_handler, bouncetime=300)

########
# Main #
########

print("Listening for button presses...")

i = 0
while True:
    time.sleep(1)
    print("%s threads running" % threading.active_count())

这是我的代码的输出:

Listening for button presses...
1 threads running
1 threads running
1 threads running
Blinking LED...
Button pressed
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
Button pressed
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
2 threads running
2 threads running
2 threads running

这让我很困惑,因为在我的真实代码中,我有一个 Ctrl+C 处理程序,上面写着:使用 threading.Event() 来通知所有线程结束,等到 active_count() == 1 (只剩下主线程),清理 GPIO 并退出。从理论上讲,这应该可以防止后台线程在清理后尝试使用 GPIO 库闪烁(这会导致异常),但实际上,它会卡住等待其他线程死亡,因为总是有 2出于某种原因。

我是不是做错了什么?还是 GPIO 库做了一些奇怪的事情?

编辑:如果我注释掉 GPIO.add_event_detect 行并改为手动调用 btn_handler 函数 (btn_handler(1)),我就不会遇到这个问题。函数完成 运行ning 后,根据 active_count(),我减少到 1 个线程。不管是什么问题,它似乎与我在 GPIO 事件处理函数中启动一个线程有关。

另请注意,如果我不在 btn_handler 中启动后台线程,active_count() 将在整个 运行 中保持为 1,据我所知, GPIO 库不运行任何后台线程。

编辑 2:另请注意,当我减少到 2 个 运行ning 线程时(当我只希望有一个时),如果我添加代码来检查线程的名称,额外的线程被称为 "Dummy-3"

RPi.GPIO's event handling is performed in a dedicated thread 隐式启动以处理正在执行的回调:

RPi.GPIO runs a second thread for callback functions. This means that callback functions can be run at the same time as your main program, in immediate response to an edge.

无论注册了多少个回调,这些线程永远只有一个:

[T]he callback functions are run sequentially, not concurrently. This is because there is only one thread used for callbacks, in which every callback is run, in the order in which they have been defined.