Linux 线程同步核心机制与实战案例总结

Linux 线程同步核心机制与实战案例总结

一、线程同步机制选择核心原则

场景需求推荐机制核心优势典型案例场景
通用互斥访问互斥锁(Mutex)阻塞式加锁,确保原子性,适用于大多数场景共享变量、文件操作、链表修改
读多写少场景读写锁(Read-Write Lock)允许多读单写,提升读并发效率配置文件读取、缓存数据访问
短临界区(纳秒级)自旋锁(Spinlock)非阻塞自旋等待,避免上下文切换开销高频小数据操作(如计数器自增)
事件通知(条件触发)条件变量(Condition Var)配合互斥锁实现线程间等待/唤醒,避免忙等待生产者-消费者模型、异步任务通知
资源计数/并发控制信号量(Semaphore)支持计数控制(如限制最大并发线程数),可跨进程连接池、任务队列并发处理
多线程阶段同步屏障(Barrier)确保所有线程到达集合点后再执行并行计算多阶段同步(如初始化+计算)
无锁编程(单变量操作)原子操作(Atomic Op)无锁高效,保证单个变量操作的原子性计数器统计、标志位状态切换
异步事件通知(I/O 兼容)eventfd文件描述符形式,支持 epoll 等多路复用,用户态高效通知异步框架、网络事件驱动

二、核心机制配合使用场景

1. 条件变量 + 互斥锁:事件驱动同步

场景:生产者-消费者模型,消费者等待生产者数据就绪。
配合逻辑
  • 互斥锁保护共享缓冲区(临界区)。
  • 条件变量用于消费者阻塞等待(缓冲区非空)和生产者通知(缓冲区有数据)。
实测案例
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer[10], count = 0;

// 生产者线程
void* producer(void* arg) {
    for (int i = 0; i < 100; i++) {
        pthread_mutex_lock(&mutex);
        buffer[count++] = i;
        pthread_cond_signal(&cond); // 通知消费者
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

// 消费者线程
void* consumer(void* arg) {
    for (int i = 0; i < 100; i++) {
        pthread_mutex_lock(&mutex);
        // 防止虚假唤醒,用 while 检查条件
        while (count == 0) {
            pthread_cond_wait(&cond, &mutex); // 释放锁并阻塞
        }
        int data = buffer[--count];
        pthread_mutex_unlock(&mutex);
        // 处理数据...
    }
    return NULL;
}

2. 读写锁 + 互斥锁:读多写少优化

场景:配置文件读取(读操作远多于写操作)。
配合逻辑
  • 读线程加读锁(共享锁),允许并发读取。
  • 写线程加写锁(独占锁),阻塞所有读/写操作。
实测案例
#include <pthread.h>

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int config_data = 0;

// 读线程(多个)
void* reader(void* arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_rwlock_rdlock(&rwlock); // 加读锁
        int data = config_data;
        pthread_rwlock_unlock(&rwlock); // 释读锁
        // 处理数据...
    }
    return NULL;
}

// 写线程(单个)
void* writer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_rwlock_wrlock(&rwlock); // 加写锁
        config_data++;
        pthread_rwlock_unlock(&rwlock); // 释写锁
    }
    return NULL;
}

3. 信号量 + 互斥锁:资源计数与互斥

场景:限制并发访问数据库连接池(最多 5 个线程同时获取连接)。
配合逻辑
  • 信号量控制并发数(初始值为 5),线程获取信号量后才能访问连接池。
  • 互斥锁保护连接池本身的操作(如连接的添加/删除)。
实测案例
#include <semaphore.h>
#include <pthread.h>

sem_t sem;           // 信号量控制并发数
pthread_mutex_t pool_mutex; // 互斥锁保护连接池
int connection_pool[5];      // 模拟连接池

void init_pool() {
    sem_init(&sem, 0, 5); // 初始 5 个可用连接
    pthread_mutex_init(&pool_mutex, NULL);
    // 初始化连接...
}

void* worker(void* arg) {
    sem_wait(&sem); // 申请连接(信号量减 1)
    pthread_mutex_lock(&pool_mutex);
    // 获取连接并操作...
    pthread_mutex_unlock(&pool_mutex);
    sem_post(&sem); // 释放连接(信号量加 1)
    return NULL;
}

4. 原子操作 + 内存屏障:无锁计数器(高性能统计)

场景:统计高频操作次数(如网络请求计数),要求无锁高效。
配合逻辑
  • 原子操作(__sync_add_and_fetch)保证自增的原子性。
  • 内存屏障(__sync_synchronize)确保多处理器间的内存可见性。
实测案例
#include <pthread.h>

volatile int counter = 0;

void* thread_func(void* arg) {
    for (int i = 0; i < 10000; i++) {
        // 原子自增并保证内存可见性
        __sync_add_and_fetch(&counter, 1);
    }
    return NULL;
}

int main() {
    pthread_t threads[10];
    for (int i = 0; i < 10; i++) {
        pthread_create(&threads[i], NULL, thread_func, NULL);
    }
    for (int i = 0; i < 10; i++) {
        pthread_join(threads[i], NULL);
    }
    printf("Total: %d\n", counter); // 输出 100000(保证原子性)
    return 0;
}

5. 屏障 + 互斥锁:多阶段并行计算同步

场景:并行计算中,所有线程完成初始化后再进入计算阶段。
配合逻辑
  • 屏障确保所有线程到达初始化完成点后再继续。
  • 互斥锁保护共享计算结果(如有)。
实测案例
#include <pthread.h>

pthread_barrier_t barrier;
pthread_mutex_t result_mutex;
int result = 0;

void* worker(void* arg) {
    int thread_id = *(int*)arg;
    // 阶段 1:初始化
    printf("Thread %d 完成初始化\n", thread_id);
    
    pthread_barrier_wait(&barrier); // 等待所有线程完成初始化
    
    // 阶段 2:计算(模拟)
    pthread_mutex_lock(&result_mutex);
    result += thread_id;
    pthread_mutex_unlock(&result_mutex);
    
    return NULL;
}

int main() {
    const int thread_count = 3;
    pthread_t threads[thread_count];
    int ids[thread_count] = {1, 2, 3};
    
    pthread_barrier_init(&barrier, NULL, thread_count);
    pthread_mutex_init(&result_mutex, NULL);
    
    for (int i = 0; i < thread_count; i++) {
        pthread_create(&threads[i], NULL, worker, &ids[i]);
    }
    
    for (int i = 0; i < thread_count; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("Result: %d\n", result); // 输出 6(1+2+3)
    return 0;
}

三、复杂场景综合应用:高并发日志系统

场景描述:

  • 多个线程并发写入日志文件,要求:
    1. 写操作互斥(避免文件指针竞争)。
    2. 日志缓冲区满时,生产者线程等待消费者线程写入磁盘。
    3. 支持动态调整并发写入线程数(通过信号量限制)。

解决方案:

机制组合
  • 互斥锁:保护日志文件句柄和缓冲区操作。
  • 条件变量:缓冲区满时生产者等待,缓冲区有空余时消费者通知生产者。
  • 信号量:限制同时写入磁盘的线程数(避免磁盘过载)。
核心代码框架
#include <pthread.h>
#include <semaphore.h>

pthread_mutex_t log_mutex;
pthread_cond_t buffer_cond;
sem_t disk_sem;          // 限制磁盘写入并发数(如 2)
char log_buffer[1024];   // 日志缓冲区
int buffer_count = 0;    // 缓冲区已用大小

void init_log_system() {
    pthread_mutex_init(&log_mutex, NULL);
    pthread_cond_init(&buffer_cond, NULL);
    sem_init(&disk_sem, 0, 2); // 最多 2 个线程同时写磁盘
}

// 生产者:添加日志到缓冲区
void add_log(const char* msg) {
    pthread_mutex_lock(&log_mutex);
    // 缓冲区满时等待
    while (buffer_count >= sizeof(log_buffer)) {
        pthread_cond_wait(&buffer_cond, &log_mutex);
    }
    strcat(log_buffer, msg);
    buffer_count += strlen(msg);
    pthread_cond_signal(&buffer_cond); // 通知消费者
    pthread_mutex_unlock(&log_mutex);
}

// 消费者:将缓冲区日志写入磁盘
void* log_consumer(void* arg) {
    while (1) {
        pthread_mutex_lock(&log_mutex);
        // 缓冲区空时等待
        while (buffer_count == 0) {
            pthread_cond_wait(&buffer_cond, &log_mutex);
        }
        char* data = log_buffer;
        int len = buffer_count;
        buffer_count = 0; // 清空缓冲区
        pthread_cond_broadcast(&buffer_cond); // 通知生产者缓冲区有空余
        pthread_mutex_unlock(&log_mutex);

        sem_wait(&disk_sem); // 申请磁盘写入权限
        write_to_disk(data, len); // 实际写入磁盘操作
        sem_post(&disk_sem); // 释放磁盘写入权限
    }
    return NULL;
}

四、关键机制对比与避坑指南

1. 性能对比(锁竞争激烈程度)

机制上下文切换忙等待适用临界区长度读并发支持
互斥锁中长不支持
读写锁中长支持
自旋锁极短(<100ns)不支持
条件变量任意依赖锁

2. 常见错误与解决方案

问题原因解决方案
死锁嵌套锁顺序不一致、锁未释放固定加锁顺序、使用 pthread_mutex_trylock
虚假唤醒条件变量未用 while 循环检查强制使用 while 而非 if 检查条件
信号量泄漏sem_wait 后异常退出未调用 sem_post使用 RAII 或异常处理保证配对调用
自旋锁性能低下临界区过长导致自旋时间过长拆分临界区,改用互斥锁

五、最佳实践总结

  1. 场景优先
    • 简单互斥用互斥锁,读多写少用读写锁,高频短操作自旋锁,事件通知条件变量,资源计数信号量。
  2. 最小化同步范围
    • 将非必要操作移到临界区外(如日志打印、复杂计算),减少锁持有时间。
  3. 无锁化优先
    • 单变量操作(如计数器)优先用原子操作,避免锁开销。
  4. 工具辅助调试
    • 使用 pthread_mutexattr_settype 设置锁类型(如递归锁),通过 strace 监控系统调用次数,perf 定位锁竞争热点。

通过合理组合同步机制,既能保证线程安全,又能最大化并发性能。实际开发中需结合具体场景,在安全性、效率和代码复杂度之间找到平衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值