内容目录:
一、多线程基本知识
二、MicroPython/Python低层多线程API介绍
三、树莓派Pico 开发板MicroPython多线程编程实践举例
3.1 Pico RP2040 MCU双核芯片多线程同步的基本原理
3.2 Pico MicroPython多线程编程实践举例
3.3 Pico RP2040 MCU双核芯片MicroPython多线程编程小结
一、多线程基本知识
一个进程(Process)就是一个正在执行的程序,每个进程都有自己独立的一块内存空间和一组系统资源。在进程的概念中,每个进程的内部数据和状态数据都是完全独立的。
在一个进程中可以包含多个线程,多个线程共享一块内存和一组系统资源。因此,系统在各个线程之间切换时,开销要比进程小得多,正因为如此,线程又称为轻量级进程。
Python程序至少有一个线程,这就是所谓的主线程,程序在启动时由Python解释器负责创建主线程,在程序结束后由Python解释器负责停止主线程。在多线程中,主线程负责其他线程的启动、挂起、停止等操作,其他线程被称为子线程。
线程管理包括线程创建、线程启动、线程休眠、等待线程结束和线程停止等操作。
二、MicroPython/Python低层多线程API介绍
MicroPython/Python中的_thread低层线程模块提供了操作多个线程(又称为轻量级进程或任务:light-weight processes or tasks)的底层多线程API(多个控制线程共享全局数据空间)。为了处理多线程同步问题,它提供了简洁的锁机制(也称为互斥锁或二进制信号量:mutexes or binary semaphores)。Python官方还提供了threading线程模块,它是基于_thread低层线程模块而构建的,threading模块提供了用于Python多线程的高级API。
-thread模块定义的常用函数如下:
(1) exception _thread.error:发生线程相关错误时抛出。
(2)_thread.LockType:锁对象的类型。
(3) _thread.start_new_thread(function, args[, kwargs]):启动新线程并返回其标识。线程执行函数 function 附带的参数列表 是args (必须是元组);可选的 kwargs 参数用于指定一个关键字参数字典。
(4)_thread.exit():抛出 SystemExit 异常。
(6)_thread.allocate_lock():分配并得到一个新的锁对象(lock object)。后面假设新的锁对象名称为lock。
对于lock锁对象,该锁对象方法如下:
a) lock.acquire(waitflag=1, timeout=-1): 没有任何可选参数时,该方法无条件申请获得锁,有必要的话会等待其他线程释放锁(同时只有一个线程能获得锁)。如果成功获取到锁返回 True,否则返回 False。
b) lock.release(): 释放锁。锁必须已经被获取(不一定是在同一个线程中获取的)。
c) lock.locked(): 返回锁的状态:如果锁被某个线程获取,则返回 True,否则返回 False。
另外,锁对象也可通过 with 语句来使用,如:
import _thread
lock = _thread.allocate_lock()
with lock:
print(“lock is locked while this executes.”)
这里要特别说明的是,在多核概念及多线程编程中,锁又称为二进制信号量(简称信号量, semaphore),因此,锁对象也叫信号量对象。关于Python低级线程API英文说明文档,可参考文献[1]。
三、树莓派Pico开发板MicroPython多线程编程实践举例
1.Pico RP2040双核芯片多线程同步的基本原理
Raspberry Pi Pico开发板的MCU采用了ARM Cortex-M0架构的RP2040芯片,该芯片拥有两个核Core0和Core1,它允许执行多线程程序。一般在MicroPython MCU应用场景下,树莓派Pico没有管理MCU及系统资源的嵌入式操作系统,因此每个核只允许执行一个线程,最多执行两个线程,见图1所示。
图1 采用锁机制的线程同步
在图1中,线程Thread1在RP2040 MCU芯片第1个核(Core0)中执行,线程Thread2在RP2040 MCU芯片第2个核(Core1)中执行,线程Thread1和线程Thread2共享一组系统资源。其中,锁Lock (又称为信号量,A.K.A Semaphore) 用于实现两个线程的同步,acquire方法用于无条件申请获得锁对象,release方法用于释放锁对象。
2.Pico MicroPython多线程编程实践举例
这里以树莓派Pico开发板扩展一只按键、两只LED灯接口电路为例(见图2),给出MicroPython多线程控制两只LED灯亮灭的程序示例。图2的上半部为Pico开发板扩展硬件接口原理图,图2的下半部为Pico开发板扩展硬件接口面包板接线图。Pico开发板扩展按键接口的方法有两种,这里采用第一种按键接口方法,关于Pico迷你开发板扩展按键接口的方法详见参考文献[2],本文不再赘述。
图2 Pico开发板扩展一只按键、两只LED灯接口电路原理图及面包板接线图
MicroPython程序示例1:使用RP2040单核;当图2中的Button按钮被按下时,两只LED灯点亮;松开Button按钮时,延时200ms,两只LED灯熄灭。
#Filename: main.py
#press_button按键采用第一种按键接口方法
import machine
import utime
led_red=machine.Pin(20,machine.Pin.OUT)
led_green=machine.Pin(17,machine.Pin.OUT)
press_button=machine.Pin(16,machine.Pin.IN, machine.Pin.PULL_UP)
while True:
if press_button.value()==0:
led_red.value(1)
led_green.value(1)
utime.sleep(0.2) #休眠200ms
led_red.value(0)
led_green.value(0)
MicroPython程序示例2:使用RP2040双核的多线程编程方法1-全局变量法
import machine
import utime
import _thread
led_red=machine.Pin(20,machine.Pin.OUT)
led_green=machine.Pin(17,machine.Pin.OUT)
press_button=machine.Pin(16,machine.Pin.IN, machine.Pin.PULL_UP)
button_pressed =False
def read_button_thread():
global button_pressed
while True:
if press_button.value()==0:
button_pressed=True
utime.sleep(0.01) #休眠10ms
_thread.start_new_thread(read_button_thread,())
while True:
if button_pressed==True:
led_red.value(1)
led_green.value(1)
utime.sleep(0.2)
button_pressed =False
led_red.value(0)
led_green.value(0)
程序示例2说明:在本程序中,read_button_thread()为自定义的读取按键线程函数,button_pressed定义为全局变量,该全局变量用于双线程操作。执行本程序后,_thread.start_new_thread方法启动读取按键线程操作read_button_thread,可理解读取按键操作是执行图1中的Thread2 (即在RP2040芯片第2个核Core1中执行);而当图2中的Button按钮被按下后的两只LED灯点亮操作是执行图1中的Thread1(即在RP2040芯片第2个核Core1中执行)。
MicroPython程序示例3:使用RP2040双核的多线程编程方法3-采用锁机制的多线程同步法
import machine
import utime
import _thread
led_red=machine.Pin(20,machine.Pin.OUT)
led_green=machine.Pin(17,machine.Pin.OUT)
lock = _thread.allocate_lock()
def led_red_thread():
while True:
lock.acquire()
led_red.value(1)
utime.sleep(0.5)
led_red.value(0)
utime.sleep(0.5)
lock.release()
_thread.start_new_thread(led_red_thread,())
while True:
while True:
lock.acquire()
led_green.value(1)
utime.sleep(0.5)
led_green.value(0)
utime.sleep(0.5)
lock.release()
程序示例3说明:在本示例程序中,led_red_thread()为自定义的控制红色LED灯发光的线程函数,_thread.allocate_lock()方法为线程分配一个名为lock的锁对象。执行本程序后,_thread.start_new_thread方法启动控制红色LED灯发光的线程操作led_red_thread,可理解红色LED灯发光的控制操作是执行图1中的Thread2 (即在RP2040芯片第2个核Core1中执行);绿色LED灯发光的控制操作是执行图1中的Thread1 (即在RP2040芯片第1个核Core0中执行)。
3. Pico RP2040 MCU双核芯片MicroPython多线程编程小结
(1)我们可以在Pico RP2040 MCU芯片的双核(Core0、Core1)上执行线程,一个线程在一个核上执行(其中一个可视作主线程,另一个被视作子线程)。
(2)我们可以使用全局变量或者锁(信号量)进行RP2040 MCU芯片双核上的线程同步。
(3)Pico RP2040 MCU仅支持两个并发线程。
(4)通常,阻塞主线程的操作应该被放到子线程中处理。
参考文献:
[1]https://docs.python.org/3/library/_thread.html
[2]博主CSDN博文.树莓派Pico迷你开发板扩展按键接口的方法及MicroPython编程.
发布日期:2021年07月06日