[Linux] Linux线程间通信与同步:原理、方法与实战

Linux线程间通信与同步:原理、方法与实战

Linux多线程编程的核心在于资源共享协作。线程间通信(IPC)和同步(Synchronization)是解决并发问题的关键。本文从原理出发,结合思维导图和代码示例,系统解析Linux线程间通信与同步的常用方法。


一、原理:共享资源与协作

线程是进程内的执行单元,共享进程的地址空间和资源(如文件描述符、全局变量等)。因此,线程间通信的本质是共享内存访问,但需要解决以下问题:

  1. 数据竞争:多个线程同时修改共享资源可能导致数据不一致。
  2. 死锁:线程间因资源互斥请求而陷入循环等待。
  3. 资源饥饿:某些线程因优先级低或资源被长期占用而无法执行。

同步目标

  • 互斥(Mutual Exclusion):确保同一时刻只有一个线程访问共享资源。
  • 同步(Synchronization):协调线程的执行顺序,确保条件满足后再执行。

二、线程间通信与同步的手段

思维导图

线程间通信与同步  
├── 同步机制  
│   ├── 互斥锁(Mutex)  
│   ├── 条件变量(Condition Variable)  
│   ├── 读写锁(Read-Write Lock)  
│   ├── 信号量(Semaphore)  
│   └── 自旋锁(Spinlock)  
├── 通信机制  
│   ├── 共享内存(Shared Memory)  
│   ├── 管道(Pipe/FIFO)  
│   ├── 消息队列(Message Queue)  
│   ├── 套接字(Socket)  
│   └── 信号(Signal)  
└── 辅助工具  
    └── 线程屏障(Thread Barrier)  

在这里插入图片描述

三、方法详解

1. 同步机制

1.1 互斥锁(Mutex)
  • 原理:通过加锁/解锁操作保护临界区,确保同一时刻只有一个线程访问共享资源。
  • 接口
    pthread_mutex_init(&mutex, NULL); // 初始化  
    pthread_mutex_lock(&mutex);       // 加锁  
    pthread_mutex_unlock(&mutex);     // 解锁  
    pthread_mutex_destroy(&mutex);    // 销毁  
    
1.2 条件变量(Condition Variable)
  • 原理:与互斥锁配合,线程在条件未满足时等待,其他线程通知后唤醒。
  • 接口
    pthread_cond_init(&cond, NULL);         // 初始化  
    pthread_cond_wait(&cond, &mutex);       // 等待条件  
    pthread_cond_signal(&cond);             // 唤醒一个线程  
    pthread_cond_broadcast(&cond);          // 唤醒所有线程  
    
1.3 信号量(Semaphore)
  • 原理:基于计数器的同步机制,支持多线程并发访问(如资源池)。
  • 接口
    sem_init(&sem, 0, 1);       // 初始化(初始值为1)  
    sem_wait(&sem);             // P操作(减1,若为0则阻塞)  
    sem_post(&sem);             // V操作(加1)  
    sem_destroy(&sem);          // 销毁  
    
1.4 读写锁(Read-Write Lock)
  • 原理:允许多个读线程同时访问,但写线程独占资源。
  • 接口
    pthread_rwlock_init(&rwlock, NULL);  
    pthread_rwlock_rdlock(&rwlock);     // 读锁  
    pthread_rwlock_wrlock(&rwlock);     // 写锁  
    pthread_rwlock_unlock(&rwlock);  
    

2. 通信机制

2.1 共享内存(Shared Memory)
  • 原理:线程直接访问共享变量,无需拷贝数据。
  • 实现:直接定义全局变量即可,需配合互斥锁保护。
2.2 管道(Pipe/FIFO)
  • 原理:通过内核缓冲区传递数据,适合父子线程通信。
  • 接口
    int pipe(int pipefd[2]); // pipefd[0]读,pipefd[1]写  
    read(pipefd[0], buffer, size);  
    write(pipefd[1], buffer, size);  
    
2.3 消息队列(Message Queue)
  • 原理:通过消息队列传递结构化数据(如POSIX消息队列)。
  • 接口
    mqd_t mq = mq_open(...);  
    mq_send(mq, buffer, size, priority);  
    mq_receive(mq, buffer, size, NULL);  
    
2.4 套接字(Socket)
  • 原理:基于网络协议(如TCP/UDP)实现跨进程/线程通信。
  • 实现:需绑定、监听、连接等步骤,适合分布式场景。
2.5 信号(Signal)
  • 原理:通过异步通知机制触发线程行为(如SIGUSR1/SIGUSR2)。
  • 接口
    signal(SIGUSR1, handler); // 注册信号处理函数  
    kill(getpid(), SIGUSR1);  // 发送信号  
    

四、完整C语言Demo(整合所有方法)

以下代码演示互斥锁、条件变量、信号量、管道、消息队列的联合使用:

#include <pthread.h>  
#include <semaphore.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <mqueue.h>  

// 共享资源  
int shared_data = 0;  
pthread_mutex_t mutex;  
pthread_cond_t cond;  
sem_t sem;  
int pipe_fd[2];  
mqd_t msg_queue;  

// 消息队列结构体  
typedef struct {  
    long mtype;  
    char mtext[100];  
} Message;  

// 线程函数1:互斥锁与条件变量  
void* thread1(void* arg) {  
    pthread_mutex_lock(&mutex);  
    printf("Thread1: Waiting for condition...\n");  
    pthread_cond_wait(&cond, &mutex);  // 等待条件  
    shared_data += 10;  
    printf("Thread1: Updated shared_data = %d\n", shared_data);  
    pthread_mutex_unlock(&mutex);  
    return NULL;  
}  

// 线程函数2:信号量  
void* thread2(void* arg) {  
    sem_wait(&sem);  // 等待信号量  
    printf("Thread2: Signal received, incrementing shared_data...\n");  
    shared_data += 20;  
    printf("Thread2: Updated shared_data = %d\n", shared_data);  
    return NULL;  
}  

// 线程函数3:管道通信  
void* thread3(void* arg) {  
    char buffer[100];  
    read(pipe_fd[0], buffer, sizeof(buffer));  // 从管道读取  
    printf("Thread3: Received from pipe: %s\n", buffer);  
    return NULL;  
}  

// 线程函数4:消息队列  
void* thread4(void* arg) {  
    Message msg;  
    mq_receive(msg_queue, msg.mtext, sizeof(msg.mtext), &msg.mtype);  
    printf("Thread4: Received message: %s\n", msg.mtext);  
    return NULL;  
}  

int main() {  
    pthread_t t1, t2, t3, t4;  

    // 初始化互斥锁与条件变量  
    pthread_mutex_init(&mutex, NULL);  
    pthread_cond_init(&cond, NULL);  

    // 初始化信号量  
    sem_init(&sem, 0, 0);  

    // 创建管道  
    pipe(pipe_fd);  

    // 创建消息队列  
    struct mq_attr attr = { .mq_flags = 0, .mq_maxmsg = 10, .mq_msgsize = sizeof(Message), .mq_curmsgs = 0 };  
    msg_queue = mq_open("/my_msg_queue", O_CREAT | O_WRONLY, 0666, &attr);  

    // 创建线程  
    pthread_create(&t1, NULL, thread1, NULL);  
    pthread_create(&t2, NULL, thread2, NULL);  
    pthread_create(&t3, NULL, thread3, NULL);  
    pthread_create(&t4, NULL, thread4, NULL);  

    // 主线程操作:触发条件变量、信号量、管道、消息队列  
    sleep(1);  // 确保其他线程已启动  

    pthread_mutex_lock(&mutex);  
    printf("Main: Notifying condition variable...\n");  
    pthread_cond_signal(&cond);  // 唤醒thread1  
    pthread_mutex_unlock(&mutex);  

    sem_post(&sem);  // 触发thread2  

    char* pipe_msg = "Hello from pipe!";  
    write(pipe_fd[1], pipe_msg, strlen(pipe_msg)+1);  // 发送管道消息  

    Message msg = { .mtype = 1, .mtext = "Hello from message queue!" };  
    mq_send(msg_queue, (char*)&msg, sizeof(msg), 1);  // 发送消息队列  

    // 等待线程结束  
    pthread_join(t1, NULL);  
    pthread_join(t2, NULL);  
    pthread_join(t3, NULL);  
    pthread_join(t4, NULL);  

    // 清理资源  
    pthread_mutex_destroy(&mutex);  
    pthread_cond_destroy(&cond);  
    sem_destroy(&sem);  
    close(pipe_fd[0]);  
    close(pipe_fd[1]);  
    mq_close(msg_queue);  
    mq_unlink("/my_msg_queue");  

    return 0;  
}  

五、编译与运行

  1. 编译命令

    gcc -o thread_sync demo.c -lpthread -lrt  
    

    -lpthread 链接线程库,-lrt 链接实时库以支持 mq_open

  2. 运行结果

    Thread1: Waiting for condition...  
    Main: Notifying condition variable...  
    Thread1: Updated shared_data = 10  
    Thread2: Signal received, incrementing shared_data...  
    Thread2: Updated shared_data = 30  
    Thread3: Received from pipe: Hello from pipe!  
    Thread4: Received message: Hello from message queue!  
    

六、阻塞与任务通知

1、阻塞与通知机制的类型

Linux中常见的线程阻塞与通知机制包括:

  1. 条件变量(Condition Variable)

    • 阻塞:线程调用 pthread_cond_wait() 等待条件成立。
    • 通知:其他线程通过 pthread_cond_signal()pthread_cond_broadcast() 唤醒等待的线程。
    • 原理:条件变量与互斥锁配合使用,确保线程在等待条件时释放锁,并在条件满足后重新获取锁。
  2. 信号量(Semaphore)

    • 阻塞:线程调用 sem_wait() 等待信号量值大于0。
    • 通知:其他线程通过 sem_post() 增加信号量值,解除阻塞。
    • 原理:信号量通过计数器控制资源访问,常用于多线程间的资源池管理。
  3. 等待队列(Wait Queue)

    • 阻塞:内核中通过 wait_event() 系列宏将进程加入等待队列。
    • 通知:调用 wake_up()wake_up_interruptible() 唤醒等待队列中的进程。
    • 场景:常用于设备驱动或内核模块中,处理硬件事件或资源就绪的通知。
  4. 信号(Signal)

    • 阻塞:线程通过 sigwait() 阻塞等待信号。
    • 通知:其他线程通过 kill()pthread_kill() 发送信号。
    • 特点:异步通知,但需谨慎处理信号处理函数的原子性。

2、条件变量与互斥锁的阻塞与通知示例

以下是一个生产者-消费者模型的代码示例,演示线程如何通过条件变量阻塞和通知协作:

代码实现
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE 5

// 共享资源
int buffer[BUFFER_SIZE];
int count = 0;          // 当前缓冲区中的元素数量
int in = 0, out = 0;    // 生产者索引、消费者索引

// 同步工具
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER;  // 缓冲区满的条件
pthread_cond_t cond_empty = PTHREAD_COND_INITIALIZER; // 缓冲区空的条件

// 生产者线程
void* producer(void* arg) {
    int item;
    while (1) {
        item = rand() % 100; // 生成随机数据

        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE) {
            // 缓冲区满,等待消费者消费
            printf("Producer: Buffer full, waiting...\n");
            pthread_cond_wait(&cond_full, &mutex); // 阻塞并释放锁
        }

        buffer[in] = item;
        in = (in + 1) % BUFFER_SIZE;
        count++;
        printf("Produced: %d\n", item);

        // 通知消费者缓冲区已更新
        pthread_cond_signal(&cond_empty);
        pthread_mutex_unlock(&mutex);

        usleep(100000); // 模拟生产延迟
    }
    return NULL;
}

// 消费者线程
void* consumer(void* arg) {
    int item;
    while (1) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {
            // 缓冲区空,等待生产者生产
            printf("Consumer: Buffer empty, waiting...\n");
            pthread_cond_wait(&cond_empty, &mutex); // 阻塞并释放锁
        }

        item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        count--;
        printf("Consumed: %d\n", item);

        // 通知生产者缓冲区有空位
        pthread_cond_signal(&cond_full);
        pthread_mutex_unlock(&mutex);

        usleep(150000); // 模拟消费延迟
    }
    return NULL;
}

int main() {
    pthread_t tid_producer, tid_consumer;

    // 创建生产者和消费者线程
    pthread_create(&tid_producer, NULL, producer, NULL);
    pthread_create(&tid_consumer, NULL, consumer, NULL);

    // 等待线程结束(实际中可能需要其他终止条件)
    pthread_join(tid_producer, NULL);
    pthread_join(tid_consumer, NULL);

    return 0;
}
代码说明
  1. 阻塞机制

    • 生产者:当缓冲区满时(count == BUFFER_SIZE),调用 pthread_cond_wait(&cond_full, &mutex) 阻塞,等待消费者消费。
    • 消费者:当缓冲区空时(count == 0),调用 pthread_cond_wait(&cond_empty, &mutex) 阻塞,等待生产者生产。
  2. 通知机制

    • 生产者:生产完成后,调用 pthread_cond_signal(&cond_empty) 通知消费者缓冲区有数据可消费。
    • 消费者:消费完成后,调用 pthread_cond_signal(&cond_full) 通知生产者缓冲区有空位可生产。
  3. 互斥锁的作用

    • 互斥锁 mutex 确保对共享资源(缓冲区 buffer 和计数器 count)的访问是线程安全的。
    • pthread_cond_wait() 在阻塞线程的同时会释放锁,允许其他线程访问共享资源。

3、信号量的阻塞与通知示例

以下是一个简单的信号量控制线程顺序执行的示例:

代码实现
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

sem_t sem1, sem2; // 两个信号量

// 线程1:先执行
void* thread1(void* arg) {
    sem_wait(&sem1); // 等待信号量sem1
    printf("Thread1: Executing first!\n");
    sem_post(&sem2); // 通知线程2
    return NULL;
}

// 线程2:后执行
void* thread2(void* arg) {
    sem_wait(&sem2); // 等待信号量sem2
    printf("Thread2: Executing after Thread1!\n");
    return NULL;
}

int main() {
    pthread_t t1, t2;

    // 初始化信号量
    sem_init(&sem1, 0, 1); // sem1初始值为1,允许线程1立即执行
    sem_init(&sem2, 0, 0); // sem2初始值为0,线程2阻塞

    // 创建线程
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 销毁信号量
    sem_destroy(&sem1);
    sem_destroy(&sem2);

    return 0;
}
代码说明
  1. 阻塞机制
    • 线程2:通过 sem_wait(&sem2) 阻塞,直到 sem2 的值变为非零。
  2. 通知机制
    • 线程1:执行完成后调用 sem_post(&sem2),将 sem2 的值加1,解除线程2的阻塞。

4、内核中的等待队列机制

在Linux内核中,等待队列(wait_queue_head_t)用于处理硬件事件或资源就绪的通知。例如,当设备驱动程序读取数据时,若数据未准备好,进程会阻塞在等待队列中,直到硬件中断触发 wake_up() 唤醒进程。

代码示例(内核模块)
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/kernel.h>

DECLARE_WAIT_QUEUE_HEAD(wq); // 定义等待队列头
atomic_t data_ready = ATOMIC_INIT(0); // 数据就绪标志

// 中断处理函数(模拟硬件事件)
void hardware_interrupt_handler(void) {
    atomic_set(&data_ready, 1); // 设置数据就绪
    wake_up(&wq); // 唤醒等待队列中的进程
}

// 用户空间阻塞函数
int wait_for_data(void) {
    wait_event_interruptible(wq, atomic_read(&data_ready)); // 阻塞直到data_ready为1
    return 0;
}
代码说明
  1. 阻塞机制
    • 用户进程调用 wait_event_interruptible(wq, condition) 阻塞,直到 conditiondata_ready)为真。
  2. 通知机制
    • 硬件中断触发后,调用 wake_up(&wq) 唤醒等待队列中的进程。

5、汇总对比

机制阻塞方式通知方式场景
条件变量pthread_cond_wait()pthread_cond_signal()生产者-消费者模型
信号量sem_wait()sem_post()资源池、线程顺序控制
等待队列wait_event()wake_up()内核驱动、硬件事件
信号sigwait()kill()异步通知(慎用)

通过合理使用这些机制,可以高效地实现线程间的协作与资源同步。


七、总结

Linux线程间通信与同步的核心在于资源共享协作控制。通过互斥锁、条件变量、信号量等同步机制,可避免数据竞争;通过管道、消息队列等通信机制,可实现灵活的数据交换。实际开发中需根据场景选择合适的方法,例如:

  • 高并发读操作:使用读写锁。
  • 跨进程通信:使用共享内存或套接字。
  • 异步通知:使用信号量或消息队列。

掌握这些技术,能显著提升多线程程序的性能与可靠性!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值