Mastering MicroPython Thread Priority

MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimized to run on microcontrollers and constrained systems. Threads in MicroPython allow concurrent execution of multiple tasks, which can significantly enhance the performance and responsiveness of your applications. Thread priority is a crucial concept when dealing with multiple threads, as it determines the order in which threads are scheduled for execution by the operating system. Understanding and properly using thread priority can help you optimize your MicroPython applications and ensure that critical tasks are executed in a timely manner.

Table of Contents#

  1. Fundamental Concepts of MicroPython Thread Priority
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts of MicroPython Thread Priority#

What are Threads?#

In MicroPython, a thread is a separate sequence of instructions that can run concurrently with other threads. Each thread has its own call stack, local variables, and program counter, allowing it to execute independently. Threads can be used to perform multiple tasks simultaneously, such as reading sensor data while controlling actuators.

What is Thread Priority?#

Thread priority is a value assigned to each thread that determines its relative importance compared to other threads. Threads with higher priority are more likely to be scheduled for execution by the operating system than threads with lower priority. In MicroPython, thread priority is typically represented as an integer value, where a higher number indicates a higher priority.

How Does Thread Scheduling Work?#

The operating system uses a scheduling algorithm to determine which thread should be executed at any given time. The scheduling algorithm takes into account the thread priority, as well as other factors such as the thread's state (running, ready, blocked) and the availability of system resources. When a thread with a higher priority becomes ready to run, the operating system may preempt a lower-priority thread and switch to the higher-priority thread.

Usage Methods#

Creating Threads in MicroPython#

To create a thread in MicroPython, you can use the _thread module. Here is a simple example:

import _thread
import time
 
# Function to be executed in the thread
def thread_function():
    for i in range(5):
        print("Thread is running:", i)
        time.sleep(1)
 
# Start a new thread
_thread.start_new_thread(thread_function, ())
 
# Main thread continues to execute
for i in range(3):
    print("Main thread is running:", i)
    time.sleep(1)
 
# Wait for a short time to allow the thread to finish
time.sleep(6)

In this example, we create a new thread using the _thread.start_new_thread() function. The first argument is the function to be executed in the thread, and the second argument is a tuple of arguments to pass to the function.

Setting Thread Priority#

In MicroPython, the _thread module does not provide a direct way to set thread priority. However, some MicroPython ports may support setting thread priority through the underlying operating system or hardware. For example, on the ESP32, you can use the uos module to set the priority of a thread:

import _thread
import time
import uos
 
# Function to be executed in the thread
def thread_function():
    for i in range(5):
        print("Thread is running:", i)
        time.sleep(1)
 
# Start a new thread
thread_id = _thread.start_new_thread(thread_function, ())
 
# Set the priority of the thread
uos.thread_priority(thread_id, 2)
 
# Main thread continues to execute
for i in range(3):
    print("Main thread is running:", i)
    time.sleep(1)
 
# Wait for a short time to allow the thread to finish
time.sleep(6)

In this example, we use the uos.thread_priority() function to set the priority of the thread to 2. The higher the priority value, the more likely the thread is to be scheduled for execution.

Common Practices#

Using Threads for Independent Tasks#

Threads are most effective when used to perform independent tasks that can be executed concurrently. For example, you can use one thread to read sensor data and another thread to control an actuator. This allows your application to make the most of the available resources and improve its responsiveness.

Avoiding Shared Resources#

When using multiple threads, it is important to avoid accessing shared resources (such as variables or hardware devices) simultaneously. This can lead to race conditions and other synchronization issues. To avoid these issues, you can use synchronization mechanisms such as locks or semaphores. Here is an example of using a lock to protect a shared variable:

import _thread
import time
 
# Shared variable
shared_variable = 0
 
# Lock for synchronization
lock = _thread.allocate_lock()
 
# Function to be executed in the thread
def thread_function():
    global shared_variable
    for i in range(5):
        # Acquire the lock
        lock.acquire()
        shared_variable += 1
        print("Thread:", shared_variable)
        # Release the lock
        lock.release()
        time.sleep(1)
 
# Start a new thread
_thread.start_new_thread(thread_function, ())
 
# Main thread continues to execute
for i in range(3):
    # Acquire the lock
    lock.acquire()
    shared_variable += 1
    print("Main thread:", shared_variable)
    # Release the lock
    lock.release()
    time.sleep(1)
 
# Wait for a short time to allow the thread to finish
time.sleep(6)

In this example, we use a lock to ensure that only one thread can access the shared variable at a time.

Best Practices#

Keeping Threads Short and Simple#

Threads should be kept short and simple to avoid resource exhaustion and improve the overall performance of your application. Long-running or complex threads can consume a large amount of system resources and make it difficult to debug and maintain your code.

Testing and Profiling#

Before deploying your application, it is important to test and profile it to ensure that the thread priority settings are working as expected. You can use tools such as the MicroPython debugger or profiling libraries to identify performance bottlenecks and optimize your code.

Conclusion#

Thread priority is an important concept in MicroPython that can help you optimize your applications and ensure that critical tasks are executed in a timely manner. While the _thread module in MicroPython does not provide a direct way to set thread priority, some MicroPython ports may support it through the underlying operating system or hardware. By following the common and best practices outlined in this blog, you can make the most of threads in MicroPython and develop high-performance applications.

References#

  1. MicroPython Documentation: https://docs.micropython.org/
  2. ESP32 MicroPython Documentation: https://docs.micropython.org/en/latest/esp32/quickref.html
  3. Python Threading Tutorial: https://realpython.com/intro-to-python-threading/