multithreading – Stopping threads started within a class in Python


I’m working on putting together an application for Raspberry Pi 3. I have experience in other languages but Python has never been a favorite of mine, thus haven’t used it much.

The threading portion involves serial communication and queuing both input and output. I’ve successfully put together a small stand-alone script that successfully does everything I want.

The problem is converting this to a class, as I haven’t yet found a way to successfully kill the threads I’ve started there. The parts I’ve written seem to work the same as the stand-alone script I made first. Using a flag to indicate when to terminate the threads worked there but doesn’t seem to in the class. Is this a scoping thing? I thought I tried using a global as well, but it may be my ignorance of the language nuances creeping in.

I’m looking for either a way to restructure this achieving the same effect, or (better) just a way to stop the threads.

The script for testing the class is very simple, with a few things I’ve tried commented out:

foo = bar.Bar()
#time.sleep(5)
#foo.stop()
#del foo
#exit()

Trying to whittle down the class to the relevant essentials (always tough to do…):

import time
import threading
import serial
import queue

global main_running

class Bar(object):

    ### A bunch of constants here
    
    def __init__(self, com_port='/dev/ttyS0'):
        global main_running
        
        self._serial_rx_queue = queue.Queue()
        self._serial_tx_queue = queue.Queue()

        self._foobar_port = serial.Serial(
            port = com_port,
            baudrate = 14400,
            parity = serial.PARITY_NONE,
            stopbits = serial.STOPBITS_ONE,
            bytesize = serial.EIGHTBITS,
            timeout = 1
        )

        main_running = True
        #self._main_running = True

        self._ping_thread = threading.Thread(target=self._ping_task)
        self._serial_rx_thread = threading.Thread(target=self._serial_rx_task)
        self._serial_tx_thread = threading.Thread(target=self._serial_tx_task)

        self._ping_thread.start()
        self._serial_rx_thread.start()
        self._serial_tx_thread.start()        

    
    ###### NOT WORKING - THREADS NEVER DIE!
    def stop(self):
        global main_running
        
        self._foobar_port.close()
        main_running = False
        #self._main_running = False
        
        self._serial_tx_thread.join()
        self._serial_rx_thread.join()
        self._ping_thread.join()
        
    ###### ALSO NOT WORKING - THREADS NEVER DIE!
#    # Destructor
#    def __del__(self):
#        self._foobar_port.close()
#        self._main_running = False
#        
#        self._serial_tx_thread.join()
#        self._serial_rx_thread.join()
#        self._ping_thread.join()
        
    ### A bunch of application specific message encoding/decoding

    def _serial_rx_task(self):
        global main_running
        
        while(self._foobar_port.isOpen() and (main_running == True)):
            if(self._foobar_port.in_waiting):
                
                if(self._foobar_port.isOpen()):
                    response = self._foobar_port.readline()
                    
                ### Application specific code to process data read
                    
            time.sleep(0.01)

    def _serial_tx_task(self):
        global main_running
        
        while(self._foobar_port.isOpen() and (main_running == True)):
            while(self._serial_tx_queue.empty() and (main_running == True)):
                time.sleep(0.01)

            message = self._serial_tx_queue.get()
            print('>>> ', message)

            if(self._foobar_port.isOpen()):
                self._foobar_port.write(message)
                
            time.sleep(0.1)

    def _ping_task(self):
        global main_running
        
#        while self._main_running:
        while main_running == True:

            self._serial_tx_queue.put('dummy message for illustration')

            time.sleep(1)
```