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

1

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

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

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

Теги:
python-3.x
multithreading
raspberry-pi3
raspberry-pi

1 ответ

3
Лучший ответ

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

RPi.GPIO запускает второй поток для функций обратного вызова. Это означает, что функции обратного вызова могут быть запущены одновременно с вашей основной программой, в немедленном ответе на ребро.

Существует только один из этих потоков, независимо от того, сколько зарегистрировано обратных вызовов:

[T] функции обратного вызова выполняются последовательно, а не одновременно. Это связано с тем, что для обратных вызовов используется только один поток, в котором выполняется каждый обратный вызов в том порядке, в котором они были определены.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню