在嵌入式Linux的C语言开发中,多线程能充分利用多核处理器,提升程序响应和吞吐能力。然而,当多个线程需要协作时,如何让它们安全、有序地"对话"就成了核心挑战。线程间通信(Inter-Thread Communication)正是解决这一难题的关键。本文将直击本质,详解基于共享内存的通信方式,并通过互斥锁与信号量,带你从理论走向实战。
一、通信基础:为何共享,为何竞争?
线程属于同一进程,因此它们天然共享进程的全局数据区 (全局变量、静态变量)和堆区(动态分配的内存)。这为通信提供了最直接的通道:读写共同的变量。
但共享带来了资源竞争 。考虑一个全局变量int counter = 0;,两个线程同时执行counter++操作。你的预期是最终counter变为2,但结果可能只是1。这是因为counter++并非原子操作(不可分割的最小操作单位),它在CPU层面可能对应"读取值→增加→写回"多步。若线程A在读值后、写回前被系统调度走,线程B开始执行并完成整个操作,那么线程A回来后会将旧值加1后写回,覆盖线程B的结果。
二、互斥锁(Mutex)
为了解决上述竞争,我们需要互斥锁。它的核心思想是为共享资源设立一个"独享门牌",一个线程持有门牌(加锁)进入临界区(访问共享资源的代码段)操作,其他线程必须等待门牌释放(解锁)后才能争夺进入。这确保了任一时刻最多只有一个线程在执行临界区代码。
关键函数(需包含<pthread.h>):
-
pthread_mutex_init(&mutex, NULL):初始化锁。 -
pthread_mutex_lock(&mutex):加锁。若锁已被持有,则调用线程阻塞。 -
pthread_mutex_unlock(&mutex):解锁。 -
pthread_mutex_destroy(&mutex):销毁锁。
示例:互斥锁保护全局变量
cpp
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int Num = 0;
pthread_mutex_t lock;
void *threadfun1(void *arg)
{
printf("线程(TID:%#x)开始执行",(unsigned int)pthread_self());
while (1)
{
pthread_mutex_lock(&lock);
Num = 100;
pthread_mutex_unlock(&lock);
}
}
void *threadfun2(void *arg)
{
printf("线程(TID:%#x)开始执行",(unsigned int)pthread_self());
while (1)
{
pthread_mutex_lock(&lock);
Num = 200;
printf("Num = %d\n",Num);
pthread_mutex_unlock(&lock);
}
}
int main(void)
{
pthread_t tid1;
pthread_t tid2;
pthread_mutex_init(&lock,NULL);
pthread_create(&tid1,NULL,threadfun1,NULL);
pthread_create(&tid2,NULL,threadfun2,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_mutex_destroy(&lock);
return 0;
}
下面是上述代码的流程图

逐行分析 C 语言代码
1. 头文件引入
#include <stdio.h> // 标准输入输出(printf)
#include <pthread.h> // POSIX 线程库(创建/管理线程)
#include <stdlib.h> // 标准库(内存分配等)
#include <string.h> // 字符串处理
#include <unistd.h> // Unix 系统 API(进程控制)
-
作用:引入程序所需的功能模块
-
关键库:
-
pthread.h:提供多线程支持(线程创建、互斥锁等) -
stdio.h:提供printf输出功能
-
2. 全局变量与互斥锁
int Num = 0; // 全局共享变量
pthread_mutex_t lock; // 定义互斥锁(保护共享资源)
-
Num:所有线程共享的整数变量
-
lock:互斥锁(mutex),确保同一时间只有一个线程访问共享资源
3. 线程函数 1:threadfun1
void *threadfun1(void *arg) {
printf("线程(TID:%#x)开始执行", (unsigned int)pthread_self());
while (1) { // 无限循环
pthread_mutex_lock(&lock); // 加锁(独占访问)
Num = 100; // 修改变量值
pthread_mutex_unlock(&lock); // 解锁(释放访问权)
}
}
-
功能:
-
打印线程 ID(
pthread_self()获取当前线程 ID) -
循环将
Num设为 100
-
-
锁机制:
-
pthread_mutex_lock():获取锁(若被占用则阻塞等待) -
pthread_mutex_unlock():释放锁
-
-
问题 :无
sleep()导致 CPU 100% 占用
4. 线程函数 2:threadfun2
void *threadfun2(void *arg) {
printf("线程(TID:%#x)开始执行", (unsigned int)pthread_self());
while (1) {
pthread_mutex_lock(&lock);
Num = 200; // 修改变量值
printf("Num = %d\n", Num); // 打印结果
pthread_mutex_unlock(&lock);
}
}
-
功能:
-
打印线程 ID
-
循环将
Num设为 200 并打印
-
-
关键区别 :包含
printf输出操作
5. 主函数 main
int main(void) {
pthread_t tid1, tid2; // 线程 ID 变量
pthread_mutex_init(&lock, NULL); // 初始化互斥锁
pthread_create(&tid1, NULL, threadfun1, NULL); // 创建线程1
pthread_create(&tid2, NULL, threadfun2, NULL); // 创建线程2
pthread_join(tid1, NULL); // 等待线程1结束
pthread_join(tid2, NULL); // 等待线程2结束
pthread_mutex_destroy(&lock); // 销毁互斥锁
return 0;
}
-
执行流程:
-
初始化互斥锁
lock -
创建两个线程:
-
tid1执行threadfun1 -
tid2执行threadfun2
-
-
主线程等待子线程结束(实际因死循环永不结束)
-
清理互斥锁(实际不会执行到这里)
-
关键机制详解
1. 互斥锁(Mutex)
-
作用:防止多个线程同时修改共享数据(竞态条件)
-
工作流程:
graph LR A[线程请求锁] --> B{锁可用?} B -->|是| C[获得锁] B -->|否| D[阻塞等待] C --> E[访问共享资源] E --> F[释放锁] F --> G[其他线程继续] -
代码体现:
-
pthread_mutex_init():创建锁 -
pthread_mutex_lock()/unlock():加锁/解锁 -
pthread_mutex_destroy():销毁锁
-
2. 线程管理
-
pthread_create():创建新线程- 参数:线程ID地址、属性、线程函数、函数参数
-
pthread_join():主线程等待子线程结束 -
pthread_self():获取当前线程ID
三、信号量(Semaphore)
互斥锁解决了安全访问问题,但线程间有时需要协同步骤,即同步。例如,线程A必须在线程B完成数据准备后才能开始处理。信号量是一个资源计数器,用于控制访问特定资源的线程数量。
关键函数(需包含<semaphore.h>):
-
sem_init(&sem, 0, initial_value):初始化信号量,initial_value为初始资源数。 -
sem_wait(&sem):申请资源(P操作)。若资源数>0,则减1并继续;若等于0,则阻塞等待。 -
sem_post(&sem):释放资源(V操作)。资源数加1,并可能唤醒等待的线程。 -
sem_destroy(&sem):销毁信号量。
示例:使用信号量控制线程执行顺序(严格交替打印ABC)
cpp
#include "head.h"
sem_t sem_1;
sem_t sem_2;
sem_t sem_3;
void *thread1(void *arg)
{
while(1)
{
sem_wait(&sem_1);
printf("A");
sem_post(&sem_2);
}
}
void *thread2(void *arg)
{
while(1)
{
sem_wait(&sem_2);
printf("B");
sem_post(&sem_3);
}
}
void *thread3(void *arg)
{
while(1)
{
sem_wait(&sem_3);
printf("C");
sem_post(&sem_1);
}
}
int main(void)
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
sem_init(&sem_1, 0, 1);
sem_init(&sem_2, 0, 0);
sem_init(&sem_3, 0, 0);
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_create(&tid3, NULL, thread3, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
sem_destroy(&sem_1);
sem_destroy(&sem_2);
sem_destroy(&sem_3);
return 0;
}
这个例子创建了一个执行链:A→B→C→A...,完美演示了信号量如何实现严格的线程同步。
四、总结与警示
-
共享内存是基础:线程通信立足于共享的全局区和堆区。
-
互斥锁用于解决竞争:确保共享数据在临界区内被安全访问,防止数据不一致。
-
信号量用于实现同步:协调线程间的执行顺序,控制对多个同类资源的访问。
-
警惕死锁 :当两个或以上线程互相持有对方所需的锁并无限等待时,就发生死锁。解决策略包括固定加锁顺序、使用超时锁(
pthread_mutex_trylock)等。
在嵌入式Linux开发中,合理运用互斥锁和信号量,是构建高效、稳定多线程程序的基石。理解它们的本质区别------锁服务于互斥 ,信号量服务于同步/计数------便能驾驭绝大多数并发场景。