一、先搞懂:多线程到底是个啥?
简单说,多线程就像给程序开了 “分身术”—— 一个程序里能同时跑多个 “小任务”(线程),每个线程各干各的活。比如你让程序一边读取传感器数据,一边往服务器发消息,要是单线程,就得等读数据完成才能发消息;但多线程一上,俩活能同步进行,再也不用 “排队等” 啦!
不过要悄悄说,Python 帮咱们把多线程的 “底层难题” 都藏起来了 —— 不用管栈大小、优先级这些复杂参数,只管专注写任务逻辑。而且每个 Python 线程,在底层 RTOS 系统里都会对应一个 “任务控制块(TCB)”,就像给每个 “分身” 发了个专属工作证,系统靠它来调度线程、分配资源,超省心~
二、线程的 “一生”:从出生到退休的 5 种状态
就像咱们打工人有 “刚入职 - 等分配任务 - 干活中 - 摸鱼中 - 离职” 的阶段,线程也有 5 种状态,摸清它们,才能更好控制线程~
-
新建状态(creation):刚创建的 “萌新线程”,还在做初始化准备,没进入工作队列;
-
就绪状态(runnable):线程已经准备好干活了,就等 CPU “点名” 分配执行资源;
-
运行状态(running):被 CPU 选中的 “幸运儿”,正在执行任务代码,是线程的 “干活时间”;
-
阻塞状态(blocked):干活途中突然 “摸鱼”—— 比如调用了 sleep(睡一会儿)、等锁或信号量,这时会放弃 CPU 使用权,直到重新回到就绪状态才有可能继续干活;
-
终止状态(dead):任务跑完了,或者意外出错 “崩了”,线程就此 “退休”,再也不能运行啦~
三、实操第一步:创建线程超简单!
Quecpython 的线程操作和常见的 open 方案差不多,核心是 “时间片轮转”——CPU 给每个线程分配一小段时间(时间片),轮流执行,看起来就像所有线程在同时跑。
1. 核心函数:_thread.start_new_thread ()
想创建线程,就靠这个函数!只需要告诉它 “要执行的任务(函数)” 和 “函数需要的参数(元组)” 就行~
作用:创建一个新线程。接收执行函数和被执行函数参数,当 function 函数无参时传入空的元组。
参数描述:
-
function - 线程执行函数。函数参数由 args 传入。
-
args - 线程执行函数的参数,类型为元组。当 function 函数无参时传入空的元组。
返回值描述:
返回创建的新线程的id。
2. 注意!栈大小可能要调整
默认情况下,线程的栈大小是 8k(8448 字节),但如果你的线程要干 “重活”—— 比如大量读写数据、处理大变量,8k 可能不够用,容易出现 “栈溢出”(内存不够用的报错)。
这时候就得用_thread.stack_size(size)调整栈大小啦!注意最小不能小于 8192 字节哦~
作用:设置或获取创建新线程使用的栈大小(以字节为单位),取决于参数 size 是否提供。默认为8448字节,最小8192字节。
参数描述:
- size - 提供该参数,用于创建新线程使用的栈大小。
返回值描述:
当参数 size 没有提供时,返回创建新线程使用的栈大小。
3. 举个栗子:两个线程同时打印
看代码,是不是超简单!两个线程分别循环打印,互不耽误~
import utime
import _thread
# 线程1的任务:循环打印10次
def thread_1():
while True:
for i in range(10):
print('thread_1', i)
utime.sleep_ms(1000) # 睡1秒,模拟干活间隙
# 线程2的任务:和线程1类似
def thread_2():
while True:
for i in range(10):
print('thread_2', i)
utime.sleep_ms(1000)
# 创建并启动两个线程,第二个参数是空元组(因为函数没参数)
_thread.start_new_thread(thread_1, ())
_thread.start_new_thread(thread_2, ())
四、线程 “不打架”:用互斥锁保护共享资源
多个线程一起干活时,最怕 “抢资源”—— 比如两个线程都要改同一个全局变量(比如计数 count),万一线程 A 刚改到一半,线程 B 就插进来改,结果肯定乱套!
这时候就得请 “互斥锁” 出场啦~它就像资源的 “独占钥匙”:线程要操作共享资源时,先拿钥匙(获取锁),用完了再还回去(释放锁),保证同一时间只有一个线程能碰资源。
1. 互斥锁的 4 个核心操作
thread.allocatelock()
创建一个互斥锁对象。
返回值描述:
返回创建的互斥锁对象。
lock.acquire()
获取锁。
返回值描述:
成功返回True,失败返回False。
lock.release()
释放锁。
thread.deletelock(lock)
删除已经创建的互斥锁。
参数描述:
- lock - 为创建时返回的互斥锁对象。
2. 举个栗子:用锁控制全局变量
下面代码里,线程 A 和 B 都要改 count,有了互斥锁,就不会出现 “计数乱跳” 的情况啦~
import _thread
import utime
# 创建互斥锁
lock = thread.allocatelock()
count = 1 # 要被两个线程操作的全局变量
# 线程B的任务:拿锁改count
def thread_entry_B(id):
global count # 声明用全局变量count
while True:
with lock: # 用with语法,自动获取锁、用完自动释放,超方便!
print(f'thread {id} count {count}.')
count += 1
utime.sleep(1) # 睡1秒,模拟处理时间
# 线程A的任务:和B一样,也要拿锁改count
def thread_entry_A(id):
global count
while True:
with lock:
print(f'thread {id} count {count}.')
count += 1
# 创建并启动线程
thread.startnew_thread(thread_entry_A, ('A',))
thread.startnew_thread(thread_entry_B, ('B',))
五、解决 “哲学家吃饭难题”:信号量来帮忙
光有互斥锁还不够!比如经典的 “哲学家吃饭问题”,就能难住互斥锁:5 个哲学家围坐,每人左右各一根筷子,要同时拿到两根才能吃饭。如果每个哲学家都先拿左边筷子,再等右边筷子,就会出现 “都拿一根、都吃不上” 的死锁情况~
这时候就得用 “信号量” 啦!它就像一个 “计数器”,能控制同时访问资源的线程数量,还能实现线程间的 “先后顺序”(比如 A 干完,B 才能干)。
1. 信号量的核心逻辑
-
信号量有个 “计数值”,范围在 0 到初始最大值之间;
-
线程 “获取信号量”(acquire):计数值减 1,要是计数值为 0,就等(阻塞);
-
线程 “释放信号量”(release):计数值加 1;
-
比如初始值设 1,信号量就变成了 “互斥锁”;初始值设 3,就允许 3 个线程同时用资源。
2. 信号量的 4 个核心操作
thread.allocatesemphore(initcount)
创建一个信号量对象。
参数描述:
initcount - 为信号量的计数初始值,也是最大值。
返回值描述:
返回创建的信号量对象。
semphore.acquire()
获取信号量。
返回值描述:
成功返回True,失败返回False。
semphore.release()
释放信号量。
semphore.getCnt()
获取信号量计数最大值和当前剩余计数值。
返回值描述:
(maxCnt, curCnt) -元组:maxCnt为计数最大值,curCnt为当前剩余计数值。
3. 举个栗子:用信号量控制线程顺序
下面代码里,线程 A 必须等线程 B 释放信号量才能跑,完美实现 “B 先干,A 再干” 的顺序~
import _thread
import utime
# 创建信号量,初始值1(最多1个线程同时用)
semphore = thread.allocatesemphore(1)
# 线程B的任务:先打印,再释放信号量
def thread_entry_B(id):
while True:
print(f'this is thread {id}.')
utime.sleep(1) # 睡1秒,模拟干活
semphore.release() # 释放信号量,让A能跑
# 线程A的任务:等信号量,拿到再打印
def thread_entry_A(id):
while True:
semphore.acquire() # 等B释放信号量
print(f'this is thread {id}.')
# 创建并启动线程
thread.startnew_thread(thread_entry_A, ('A',))
thread.startnew_thread(thread_entry_B, ('B',))
4. 哲学家吃饭问题怎么解?
有了信号量,解决死锁超简单:
-
方案 1:搞个 “全局锁”,哲学家要拿筷子前,先拿全局锁,确保同一时间只有一个人拿筷子,就不会死锁;
-
方案 2:给每根筷子配一个信号量,让哲学家拿筷子时按 “先左后右” 或 “先右后左” 的固定顺序拿,避免互相抢。
最后小结
Quecpython 多线程其实没那么难~记住这 3 个关键点:
-
创建线程用_thread.start_new_thread(),重要:调栈大小;
-
共享资源怕打架?用互斥锁(allocate_lock())保护;
-
要控顺序、防死锁?信号量(allocate_semphore())来救场。
赶紧动手试试代码,让你的程序 “跑” 得更高效吧!如果有问题,欢迎在评论区交流哦~






