2. 多线程并发打印
任务目标
在 Zephyr RTOS 中创建 3 个不同优先级的线程,各自循环打印独立消息,通过串口终端观察线程调度顺序与时间片轮转效果。本案例聚焦于线程创建、优先级配置与调度行为验证。
环境依赖
- Zephyr RTOS 3.x(以
main分支为例) - 开发板:
qemu_x86(无需真实硬件,便于快速验证) - west 工作流已配置完成
完整实现
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
/* 线程栈与线程控制块 */
#define STACK_SIZE 512
#define THREAD_NUM 3
K_THREAD_STACK_DEFINE(thread_stack_0, STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_stack_1, STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_stack_2, STACK_SIZE);
struct k_thread thread_data_0;
struct k_thread thread_data_1;
struct k_thread thread_data_2;
/* 线程入口函数 */
void thread_entry(void *p1, void *p2, void *p3)
{
const char *msg = (const char *)p1;
int id = (int)(intptr_t)p2;
while (1) {
printk("[Thread %d] %s (priority: %d)\n",
id, msg, k_thread_priority_get(k_current_get()));
k_msleep(100); /* 主动让出 CPU,便于观察调度 */
}
}
void main(void)
{
printk("Main: starting multi-thread demo...\n");
/* 创建 3 个线程,优先级分别为 2、3、4(数值越小优先级越高) */
k_thread_create(&thread_data_0, thread_stack_0, STACK_SIZE,
thread_entry, (void *)"Hello from T0", (void *)0, NULL,
2, 0, K_NO_WAIT);
k_thread_create(&thread_data_1, thread_stack_1, STACK_SIZE,
thread_entry, (void *)"Hello from T1", (void *)1, NULL,
3, 0, K_NO_WAIT);
k_thread_create(&thread_data_2, thread_stack_2, STACK_SIZE,
thread_entry, (void *)"Hello from T2", (void *)2, NULL,
4, 0, K_NO_WAIT);
/* 主线程退出,让调度器运行上述线程 */
}
构建与运行
# 在项目根目录下执行
west build -b qemu_x86 -p always
west build -t run
验证方法与判定标准
- 观察串口输出:终端应持续打印
[Thread 0]、[Thread 1]、[Thread 2]三种消息,每条消息间隔约 100ms。 - 调度顺序验证:由于线程 0 优先级最高(数值 2 最小),在无其他阻塞时它应最先被调度;随后线程 1、线程 2 依次获得 CPU。由于每个线程执行
k_msleep(100)主动让出,实际输出会呈现近似轮转的交替顺序,而非严格按优先级连续执行。 - 优先级抢占验证:若将线程 0 的
k_msleep改为k_yield(),可观察到高优先级线程 0 会持续抢占 CPU,线程 1 和 2 几乎无法运行——这验证了优先级抢占调度机制。 - 通过条件:终端能稳定看到 3 个线程的交替打印,且每条消息中的优先级数值与创建时配置一致(2、3、4),即视为通过。
预期输出(节选)
Main: starting multi-thread demo...
[Thread 0] Hello from T0 (priority: 2)
[Thread 1] Hello from T1 (priority: 3)
[Thread 2] Hello from T2 (priority: 4)
[Thread 0] Hello from T0 (priority: 2)
[Thread 1] Hello from T1 (priority: 3)
...
关键说明
K_NO_WAIT表示线程创建后立即进入就绪队列,无需延迟启动。- 线程入口函数的
p2参数通过整数转换传递线程 ID,用于打印区分。 - 若需观察时间片轮转(同优先级线程),可将 3 个线程设为相同优先级(如均为 4),并移除
k_msleep,此时 CONFIG_TIMESLICE_SIZE 决定每个线程的运行时长。