Почему я получаю дополнительный поток, работающий в Python?

Я работаю с Raspberry Pi.

У меня есть кнопка, подключенная к контакту GPIO, и светодиод, подключенный к другому контакту. При нажатии кнопки вызывается функция. Пока эта функция активна, я хочу, чтобы светодиод мигал, для чего требуется фоновый поток. По сути, это означает, что мне нужен фоновый поток для запуска, пока работает мой обработчик кнопки, и для остановки, когда мой обработчик кнопки останавливается.

Проблема, с которой я сталкиваюсь, демонстрируется запуском кода ниже. Код начинается с одного потока, но когда я нажимаю кнопку, threading.active_count() показывает, что запущено 3 потока (а не 2, как ожидалось). Когда мой поток завершен, у меня остается 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)), у меня не будет этой проблемы. После того, как функция завершится, у меня останется 1 поток согласно active_count(). В чем бы ни была проблема, похоже, она связана с тем, что я запускаю поток в функции обработчика событий GPIO.

Также обратите внимание, что если я не запускаю фоновый поток в btn_handler, active_count() остается равным 1 на протяжении всего выполнения, поэтому, насколько я могу судить, библиотека GPIO не запускает никаких фоновых потоков.

Редактировать 2: Также обратите внимание, что когда у меня работает до 2 потоков (когда я ожидаю, что будет только один), если я добавлю код для проверки имен потоков, дополнительный поток называется "Dummy-3"

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
386
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

RPi.GPIO обработка событий выполняется в выделенном потоке запускается неявно для обработки выполняемых обратных вызовов:

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.

Мне кажется странным, что пока я на самом деле не нажму кнопку и не сработает мой btn_handler, существует только один поток (согласно active_count()) — основной поток. Если есть отдельный поток, в котором работает RPi.GPIO, который следит за границей и вызывает функции обратного вызова, разве он не должен запускаться, как только вы регистрируете первую функцию обратного вызова с помощью GPIO.add_event_detect?

John 12.03.2019 23:24

@John: я не знаю внутренностей, но, предположительно, фактические события основаны на прерываниях, но, чтобы не вызывать проблем с основным потоком, когда основной поток получает прерывание, связанное с обратным вызовом, он просто ставит событие в очередь. информация обратного звонка вверх. Если поток обработки событий не существует, он запускается, когда происходит первое событие с обратным вызовом, чтобы начать обработку очереди.

ShadowRanger 12.03.2019 23:34

Другие вопросы по теме