玩转 Quecpython 多线程!小白也能轻松上手的并发编程指南

一、先搞懂:多线程到底是个啥?

简单说,多线程就像给程序开了 “分身术”—— 一个程序里能同时跑多个 “小任务”(线程),每个线程各干各的活。比如你让程序一边读取传感器数据,一边往服务器发消息,要是单线程,就得等读数据完成才能发消息;但多线程一上,俩活能同步进行,再也不用 “排队等” 啦!

不过要悄悄说,Python 帮咱们把多线程的 “底层难题” 都藏起来了 —— 不用管栈大小、优先级这些复杂参数,只管专注写任务逻辑。而且每个 Python 线程,在底层 RTOS 系统里都会对应一个 “任务控制块(TCB)”,就像给每个 “分身” 发了个专属工作证,系统靠它来调度线程、分配资源,超省心~

二、线程的 “一生”:从出生到退休的 5 种状态

就像咱们打工人有 “刚入职 - 等分配任务 - 干活中 - 摸鱼中 - 离职” 的阶段,线程也有 5 种状态,摸清它们,才能更好控制线程~

  1. 新建状态(creation):刚创建的 “萌新线程”,还在做初始化准备,没进入工作队列;

  2. 就绪状态(runnable):线程已经准备好干活了,就等 CPU “点名” 分配执行资源;

  3. 运行状态(running):被 CPU 选中的 “幸运儿”,正在执行任务代码,是线程的 “干活时间”;

  4. 阻塞状态(blocked):干活途中突然 “摸鱼”—— 比如调用了 sleep(睡一会儿)、等锁或信号量,这时会放弃 CPU 使用权,直到重新回到就绪状态才有可能继续干活;

  5. 终止状态(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 个关键点:

  1. 创建线程用_thread.start_new_thread(),重要:调栈大小;

  2. 共享资源怕打架?用互斥锁(allocate_lock())保护;

  3. 要控顺序、防死锁?信号量(allocate_semphore())来救场。

赶紧动手试试代码,让你的程序 “跑” 得更高效吧!如果有问题,欢迎在评论区交流哦~