《lvgl基础学习 —— 多线程》

1.为什么要关注lvgl里的多线程

LVGL 不是线程安全的。
任何 LVGL API,只能在“UI 线程”里调用。

允许的:

  • 多线程做 I/O、算法、驱动

  • 多线程通过消息通知 UI

禁止的:

  • 在子线程里 lv_label_set_text

  • 在子线程里 lv_scr_load

  • 在子线程里创建/删除对象

👉 违反一次,迟早随机崩溃

 

原因不是“LVGL 不行”,而是:

  • LVGL 内部:

    • 单链表

    • 全局状态

    • 无锁设计(为了快)

  • 加锁会严重影响性能(尤其嵌入式)

👉 官方选择:单线程 UI + 外部并发

 

2.LVGL中正确的多线程模型(建议都这么用)

┌──────────────┐
│  UI Thread   │  ← lv_timer_handler()
│  (LVGL only) │  ← 所有 lv_* API
└──────▲───────┘
       │ 消息
┌──────┴───────┐
│ Worker Thread│  ← 硬件 / 网络 / 算法
│ (no LVGL)    │
└──────────────┘

一句话:

子线程只“算/读/等”,
UI 线程只“画/显示/交互”。

 

3.三种”线程 <-----> UI“通信方式

方式1:消息队列 + UI轮询

子线程:

post_msg(MSG_UPDATE_TEXT, value);

UI线程:

while(fetch_msg(&msg)) {
    lv_label_set_text_fmt(label, "%d", msg.value);
}

  优点:简单、稳定、不用锁LVGL

方式2:lv_async_call()(LVGL 官方接口)

  让子线程”请求UI线程执行一个函数“

demo:

static void ui_update_cb(void *data)
{
    int val = (int)data;
    lv_label_set_text_fmt(label, "%d", val);
}

/* 子线程 */
lv_async_call(ui_update_cb, (void *)value);

注意:

  • 回调在 UI 线程 执行

  • 不适合高频调用(比如 100Hz)

方式3:直接加muxer锁LVGL(不推荐)

pthread_mutex_lock(&lvgl_lock);
lv_label_set_text(label, "xxx");
pthread_mutex_unlock(&lvgl_lock);

  很容易死锁
  官方不推荐
  实际工程很难维护

 

 4.完整多线程demo

场景

 

  • UI:显示一个数值

  • 子线程:每 1 秒递增

  • UI 不卡

1.消息定义

typedef enum {
    MSG_UPDATE_VALUE,
} msg_type_t;

typedef struct {
    msg_type_t type;
    int value;
} app_msg_t;

2.子线程(woker.c)

#include <pthread.h>
#include <unistd.h>
#include "app_msg.h"

static void *worker_thread(void *arg)
{
    int val = 0;

    while(1) {
        sleep(1);
        val++;

        app_msg_t msg = {
            .type = MSG_UPDATE_VALUE,
            .value = val,
        };
        app_msg_post(&msg);
    }
    return NULL;
}

void worker_start(void)
{
    pthread_t tid;
    pthread_create(&tid, NULL, worker_thread, NULL);
}

3.ui层(ui.c)

#include "lvgl.h"
#include "app_msg.h"

static lv_obj_t *label;

void ui_counter_init(void)
{
    label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Value: 0");
    lv_obj_center(label);
}

void ui_counter_process_msg(app_msg_t *msg)
{
    if(msg->type == MSG_UPDATE_VALUE) {
        lv_label_set_text_fmt(label, "Value: %d", msg->value);
    }
}

4.main.c

int main(void)
{
    lv_init();
    hal_init();

    app_msg_init();
    ui_counter_init();
    worker_start();

    app_msg_t msg;

    while(1) {
        lv_timer_handler();

        while(app_msg_fetch(&msg) == 0) {
            ui_counter_process_msg(&msg);
        }

        usleep(5000);
    }
}

 

5.lv_timer VS 线程(区分清楚)

❌ 错误理解

“我用线程,其实是为了定时”

✅ 正确用法

 

  • UI 定时lv_timer_create()

  • 后台任务:线程

lv_timer_create(ui_refresh_cb, 1000, NULL);

  不要用线程干UI定时的活。

 

最常见的致命坑(一定要避)

 

❌ 子线程调用任何 lv_*
❌ 消息里传指针(生命周期错)
❌ UI 线程 sleep 很久
❌ worker 线程里 printf 太多(卡)
❌ 多线程同时改全局状态

 

 

 

 

 

 

 

 

 

 

 

 

五、一个“完整可跑”的多线程 Demo(强烈建议照着做)

场景

  • UI:显示一个数值

  • 子线程:每 1 秒递增

  • UI 不卡


1️⃣ 消息定义(沿用上一课)

 
typedef enum { MSG_UPDATE_VALUE, } msg_type_t; typedef struct { msg_type_t type; int value; } app_msg_t;

2️⃣ 子线程(worker.c)

 
#include <pthread.h> #include <unistd.h> #include "app_msg.h" static void *worker_thread(void *arg) { int val = 0; while(1) { sleep(1); val++; app_msg_t msg = { .type = MSG_UPDATE_VALUE, .value = val, }; app_msg_post(&msg); } return NULL; } void worker_start(void) { pthread_t tid; pthread_create(&tid, NULL, worker_thread, NULL); }

3️⃣ UI 层(ui_counter.c)

 
#include "lvgl.h" #include "app_msg.h" static lv_obj_t *label; void ui_counter_init(void) { label = lv_label_create(lv_scr_act()); lv_label_set_text(label, "Value: 0"); lv_obj_center(label); } void ui_counter_process_msg(app_msg_t *msg) { if(msg->type == MSG_UPDATE_VALUE) { lv_label_set_text_fmt(label, "Value: %d", msg->value); } }

4️⃣ Service/UI 线程轮询消息(main.c)

 
int main(void) { lv_init(); hal_init(); app_msg_init(); ui_counter_init(); worker_start(); app_msg_t msg; while(1) { lv_timer_handler(); while(app_msg_fetch(&msg) == 0) { ui_counter_process_msg(&msg); } usleep(5000); } }
posted @ 2025-12-23 18:12  一个不知道干嘛的小萌新  阅读(2)  评论(0)    收藏  举报