news 2026/7/22 18:12:47

嵌入式多线程开发:pthread 线程与互斥锁实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式多线程开发:pthread 线程与互斥锁实战

嵌入式多线程开发:pthread 线程与互斥锁实战

一、引言

上一篇文章我们讨论了fork多进程开发。进程固然好——每个进程独立地址空间,崩溃不互相影响——但创建开销大、进程间通信(IPC)麻烦。在很多嵌入式场景中,我们需要轻量级的并行,这时多线程就登场了。

线程(Thread)是共享同一进程地址空间的执行流。多个线程之间切换开销远小于进程切换,而且可以直接访问同一块内存,通信几乎零成本——但也正因如此,必须用互斥锁等手段保护共享数据。

本文从pthread的创建讲起,深入互斥锁的原理与死锁排查,最后给出一个传感器数据采集+并发的实战模型。

二、线程 vs 进程:一张表看懂

对比维度进程线程
创建开销大(分配独立地址空间)小(共享地址空间)
切换开销大(切换页表)小(只切换寄存器和栈)
内存共享IPC 手段(管道/共享内存)天然共享全局变量和堆
崩溃影响不影响其他进程一个线程崩溃可能拖垮整个进程
适用场景隔离性要求高高并发、实时性要求高

在嵌入式 Linux 中,线程的优势尤为明显:传感器线程、网络通信线程、数据处理线程可以各司其职,互不阻塞。

三、创建线程:pthread_create

#include<pthread.h>intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);

看起来参数多,拆解一下:

  1. thread:输出参数,保存新线程的 ID
  2. attr:线程属性(NULL 表示默认)
  3. start_routine:线程入口函数,必须是void* func(void* arg)签名
  4. arg:传给入口函数的参数

编译时需要链接 pthread 库:

gcc-oapp main.c-lpthread

基础示例

#include<stdio.h>#include<pthread.h>#include<unistd.h>void*sensor_task(void*arg){intid=*(int*)arg;for(inti=0;i<5;i++){printf("[传感器%d] 第%d次采样\n",id,i+1);sleep(1);}printf("[传感器%d] 采集结束\n",id);returnNULL;}intmain(void){pthread_ttid1,tid2;intid1=1,id2=2;pthread_create(&tid1,NULL,sensor_task,&id1);pthread_create(&tid2,NULL,sensor_task,&id2);// 主线程等待两个子线程结束pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("所有传感器采集完成\n");return0;}

pthread_join会阻塞当前线程直到目标线程结束——类似于进程中的waitpid

四、互斥锁:保护共享数据

多线程同时访问同一变量时,会出现竞态条件(Race Condition)。想象两个线程同时对counter++操作:

线程A: 读 counter(=0) → 加1(=1) → 写 counter(=1) 线程B: 读 counter(=0) → 加1(=1) → 写 counter(=1)

本应是 2,结果却是 1——这就是数据竞争。解决方式是互斥锁(Mutex)。

#include<pthread.h>pthread_mutex_tlock=PTHREAD_MUTEX_INITIALIZER;intshared_counter=0;void*worker(void*arg){for(inti=0;i<100000;i++){pthread_mutex_lock(&lock);// 加锁shared_counter++;// 临界区pthread_mutex_unlock(&lock);// 解锁}returnNULL;}

互斥锁像厕所的锁——一个人进去了就锁门,别人在外面等着。出来解锁后,下一个人才能进去。

互斥锁的四种操作

函数行为
pthread_mutex_lock阻塞等待直到拿到锁
pthread_mutex_trylock立即返回,拿不到锁返回 EBUSY
pthread_mutex_unlock释放锁
pthread_mutex_destroy销毁锁(用动态方式创建的)

静态初始化用PTHREAD_MUTEX_INITIALIZER,动态初始化用pthread_mutex_init

五、死锁:当两把锁互相等待

这是多线程编程中最让人头疼的问题。看这段代码:

pthread_mutex_tlockA=PTHREAD_MUTEX_INITIALIZER;pthread_mutex_tlockB=PTHREAD_MUTEX_INITIALIZER;// 线程1void*thread1(void*arg){pthread_mutex_lock(&lockA);// 拿到 Asleep(1);// 给线程2时间去拿 Bpthread_mutex_lock(&lockB);// 等待 B...// ...}// 线程2void*thread2(void*arg){pthread_mutex_lock(&lockB);// 拿到 Bsleep(1);// 给线程1时间去拿 Apthread_mutex_lock(&lockA);// 等待 A...// ...}

结果:线程1 持有 A 等 B,线程2 持有 B 等 A——谁也等不到谁,死锁

避免死锁的黄金法则

  1. 固定加锁顺序:所有线程必须按相同顺序获取多把锁
  2. 限制锁粒度:锁保护的代码越少越好
  3. 使用pthread_mutex_trylock:拿不到就放弃,稍后重试
  4. 超时机制:部分 pthread 实现支持pthread_mutex_timedlock

固定加锁顺序举例:

// 无论哪个线程,都先锁 lockA 再锁 lockBpthread_mutex_lock(&lockA);pthread_mutex_lock(&lockB);// 临界区...pthread_mutex_unlock(&lockB);pthread_mutex_unlock(&lockA);

六、实战:传感器采集 + 线程池模型

下面是一个简化版的多线程传感器采集架构:

#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>#defineSENSOR_COUNT3pthread_mutex_tdata_lock=PTHREAD_MUTEX_INITIALIZER;// 共享数据缓冲区typedefstruct{floattemperature;floathumidity;floatpressure;intupdated;}SensorData;SensorData g_data={0};void*temp_sensor(void*arg){(void)arg;while(1){pthread_mutex_lock(&data_lock);g_data.temperature=25.0+(rand()%100)/10.0;g_data.updated=1;pthread_mutex_unlock(&data_lock);sleep(2);}returnNULL;}void*report_thread(void*arg){(void)arg;while(1){pthread_mutex_lock(&data_lock);if(g_data.updated){printf("[上报] 温度=%.1f°C\n",g_data.temperature);g_data.updated=0;}pthread_mutex_unlock(&data_lock);sleep(3);}returnNULL;}intmain(void){pthread_tt1,t2;pthread_create(&t1,NULL,temp_sensor,NULL);pthread_create(&t2,NULL,report_thread,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);return0;}

这个架构很简单但实用:采集线程定时更新共享数据,上报线程定时读取并上报,靠互斥锁保证数据一致性。

七、总结

多线程编程的核心就三件事:

  1. 创建与回收pthread_create/pthread_join
  2. 数据保护:互斥锁pthread_mutex_lock/unlock
  3. 死锁避免:固定加锁顺序、减小锁粒度、用 trylock

在嵌入式 Linux 中,线程是并行处理的主力。掌握互斥锁,你的程序才能在高并发下稳如磐石。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 18:12:17

Jellium Desktop睡眠模式控制:如何防止播放时电脑休眠

Jellium Desktop睡眠模式控制&#xff1a;如何防止播放时电脑休眠 【免费下载链接】jellium-desktop An unofficial desktop client for Jellyfin 项目地址: https://gitcode.com/GitHub_Trending/je/jellium-desktop Jellium Desktop是一款非官方的Jellyfin桌面客户端&…

作者头像 李华
网站建设 2026/7/22 18:12:11

SoC时钟与复位管理深度解析:从DPLL配置到PRCM复位序列实战

1. 项目概述与核心价值在任何一个复杂的数字芯片&#xff0c;尤其是高性能的片上系统&#xff08;SoC&#xff09;里&#xff0c;时钟和复位就像是整个系统的“心跳”与“脉搏”。心跳乱了&#xff0c;系统就会功能失常&#xff1b;脉搏停了&#xff0c;系统就彻底“休克”。我…

作者头像 李华
网站建设 2026/7/22 18:12:04

用Docling+Spring AI搭建PDF解析与RAG入库管道:表格、Metadata和质量门禁

文章摘要 Spring AI提供DocumentReader、DocumentTransformer和VectorStore等ETL能力&#xff0c;但复杂PDF的版面、表格和OCR往往需要更专业的解析工具。Docling可以将PDF解析成包含页面、标题、段落、表格和图片信息的结构化文档。本文通过“Python Docling解析服务&#xff…

作者头像 李华
网站建设 2026/7/22 18:08:31

掌握AI大模型,抢占2026年程序员高薪风口!收藏这份进阶指南

随着AI应用层成为2026年行业最大机遇&#xff0c;大模型岗位需求激增&#xff0c;年薪破百万。企业推进AI产品落地&#xff0c;但能交付的大模型应用开发工程师极度稀缺。 2026年AI行业最大的机会&#xff0c;毫无疑问就在应用层&#xff01; 字节跳动已有7个团队全速布局Agent…

作者头像 李华
网站建设 2026/7/22 18:07:49

【WorkBuddy从入门到精通实战教程】使用手册 第 8 章 WorkBuddy 接入小程序与 IM 助理

小程序的两种模式 模式 任务在哪里运行 是否依赖电脑在线 适合任务 本机模式 已连接的电脑 是 本地文件、本地 Skill、已有工作区 云端模式 隔离的云端环境 否 调研、写作、临时分析、并行任务 首次使用 通过官方入口打开 WorkBuddy 小程序并登录; 查看当前处于本机还是云端模…

作者头像 李华
网站建设 2026/7/22 18:06:58

TDDD命令行详解:掌握tddd:watch与tddd:test提升开发效率

TDDD命令行详解&#xff1a;掌握tddd:watch与tddd:test提升开发效率 【免费下载链接】tddd A Laravel Continuous Integration Package 项目地址: https://gitcode.com/gh_mirrors/tdd/tddd TDDD&#xff08;A Laravel Continuous Integration Package&#xff09;是一款…

作者头像 李华