Does MicroPython Thread Well?
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 optimised to run on microcontrollers and constrained systems. One of the questions developers often ask is whether MicroPython can handle threading effectively. Threading allows multiple parts of a program to run concurrently, potentially improving the performance and responsiveness of an application. In this blog post, we will explore the fundamental concepts, usage methods, common practices, and best practices of threading in MicroPython.
Table of Contents#
- Fundamental Concepts of Threading in MicroPython
- Usage Methods
- Common Practices
- Best Practices
- Conclusion
- References
Fundamental Concepts of Threading in MicroPython#
What is Threading?#
Threading is a technique for achieving concurrent execution within a single process. In a multithreaded program, multiple threads can run simultaneously, sharing the same resources such as memory and file descriptors. Each thread has its own call stack and program counter, allowing it to execute independently.
Threading in MicroPython#
MicroPython supports threading through the _thread module (in some MicroPython ports, it may be named thread). However, it's important to note that the threading support in MicroPython is more limited compared to full - fledged Python implementations. This is because MicroPython is designed to run on resource - constrained devices, and full - blown threading can be resource - intensive.
The _thread module provides basic functionality for creating and managing threads. You can create a new thread by calling the start_new_thread function, which takes a function to run in the new thread and a tuple of arguments for that function.
Usage Methods#
Creating a Simple Thread#
Here is a simple example of creating a thread in MicroPython:
import _thread
import time
# Function to be run in the new 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 its work
for i in range(3):
print("Main thread is running: ", i)
time.sleep(1)
# Keep the main thread alive for a while to let the other thread finish
time.sleep(5)In this example, we first define a function thread_function that will be run in a new thread. We then use the _thread.start_new_thread function to start a new thread and pass the thread_function and an empty tuple of arguments. The main thread continues to execute its own loop, and we use time.sleep to keep the main thread alive long enough for the new thread to finish.
Common Practices#
Synchronization#
When multiple threads access shared resources, synchronization becomes crucial to avoid race conditions. A race condition occurs when two or more threads access a shared resource simultaneously, and the final outcome depends on the relative timing of the threads.
MicroPython provides basic synchronization primitives such as locks. Here is an example of using a lock to protect a shared variable:
import _thread
import time
# Shared variable
shared_variable = 0
# Create a lock
lock = _thread.allocate_lock()
# Function to increment the shared variable
def increment_variable():
global shared_variable
for i in range(5):
lock.acquire()
shared_variable += 1
print("Thread: Shared variable is ", shared_variable)
lock.release()
time.sleep(1)
# Start a new thread
_thread.start_new_thread(increment_variable, ())
# Main thread also accesses the shared variable
for i in range(3):
lock.acquire()
shared_variable += 1
print("Main thread: Shared variable is ", shared_variable)
lock.release()
time.sleep(1)
# Keep the main thread alive for a while to let the other thread finish
time.sleep(5)In this example, we use a lock to ensure that only one thread can access and modify the shared_variable at a time. The lock.acquire() method is used to acquire the lock, and the lock.release() method is used to release the lock.
Best Practices#
Resource Management#
- Limit the number of threads: Since MicroPython runs on resource - constrained devices, creating too many threads can lead to resource exhaustion. Only create threads when necessary.
- Properly manage memory: Threads consume memory, especially their call stacks. Be aware of the memory usage of your threads and try to minimise it.
Error Handling#
- Handle exceptions in threads: Exceptions in threads can cause the thread to terminate unexpectedly. Make sure to handle exceptions properly within each thread to avoid crashing the entire program.
import _thread
import time
def thread_function():
try:
for i in range(5):
print("Thread is running: ", i)
time.sleep(1)
except Exception as e:
print("Exception in thread: ", e)
_thread.start_new_thread(thread_function, ())
# Main thread
time.sleep(5)Conclusion#
MicroPython does support threading, but its threading capabilities are more limited compared to full - fledged Python implementations. Threading in MicroPython can be useful for certain applications, such as handling multiple input sources or performing background tasks. However, developers need to be aware of the resource constraints and use proper synchronization techniques to ensure the reliability of their programs.
References#
- MicroPython official documentation: https://docs.micropython.org/
- Python threading documentation: https://docs.python.org/3/library/threading.html (for general threading concepts)