Linux多线程通信:告别数据混乱

在嵌入式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);    // 解锁(释放访问权)
    }
}
  • 功能

    1. 打印线程 ID(pthread_self()获取当前线程 ID)

    2. 循环将 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);
    }
}
  • 功能

    1. 打印线程 ID

    2. 循环将 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;
}
  • 执行流程

    1. 初始化互斥锁 lock

    2. 创建两个线程:

      • tid1执行 threadfun1

      • tid2执行 threadfun2

    3. 主线程等待子线程结束(实际因死循环永不结束)

    4. 清理互斥锁(实际不会执行到这里)


关键机制详解

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开发中,合理运用互斥锁和信号量,是构建高效、稳定多线程程序的基石。理解它们的本质区别------锁服务于互斥 ,信号量服务于同步/计数------便能驾驭绝大多数并发场景。

相关推荐
三天不学习2 小时前
Linux inotify 机制详解,解决“用户实例限制”问题
linux·运维·c#
ZFB00012 小时前
【麒麟桌面系统】V10-SP1 2503 系统知识——插入U盘(移动硬盘)为只读状态
linux·运维·kylin
unfeeling_2 小时前
Keepalived实验
linux·服务器·网络
Web极客码2 小时前
解决WordPress后台“外观”菜单消失
linux·服务器·wordpress
熬夜有啥好2 小时前
Linux软件编程——综合小练习
linux·算法·目录遍历·fgets·strcpy·linux内核与用户交互·strtok
qizhideyu2 小时前
LVS(Linux virual server)
linux·运维·lvs
熬夜有啥好2 小时前
Linux软件编程——线程间的通信
互斥锁·信号量·软件编程·多线程间的通信
xiaoliuliu123453 小时前
CentOS 7 安装 gcc-4.8.5-44.el7.x86_64.rpm 详细步骤(含依赖解决)
linux·运维·centos
青桔柠薯片3 小时前
Linux软件编程:线程和进程间通信
linux·开发语言·线程·进程