Linux 线程同步:锁的应用总结

Linux 线程同步:锁的应用总结

一、互斥锁(Mutex)

1. 基本概念

  • 作用:确保同一时间只有一个线程访问共享资源,实现互斥访问。
  • 核心特性:原子性、阻塞式加锁(未获取锁时线程进入睡眠)。
  • 适用场景:保护临界区(如共享变量、文件操作、链表操作等)。

2. 关键 API

函数原型说明
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);初始化互斥锁,attr 可设为 NULL(默认属性)。
int pthread_mutex_lock(pthread_mutex_t *mutex);加锁:若锁被占用,线程阻塞等待。
int pthread_mutex_trylock(pthread_mutex_t *mutex);尝试加锁:若锁可用则获取,否则返回 EBUSY(非阻塞)。
int pthread_mutex_unlock(pthread_mutex_t *mutex);解锁:释放锁,唤醒等待线程。

3. 示例代码

#include <pthread.h>

pthread_mutex_t mutex;
int shared_data = 0;

void* thread_func(void* arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_mutex_lock(&mutex);   // 进入临界区
        shared_data++;
        pthread_mutex_unlock(&mutex); // 退出临界区
    }
    return NULL;
}

int main() {
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
    // 创建线程...
    pthread_mutex_destroy(&mutex);    // 销毁锁(需确保无线程持有)
    return 0;
}

4. 注意事项

  • 死锁风险:避免多个线程以不同顺序获取多个互斥锁(可通过固定加锁顺序解决)。
  • 性能开销:锁竞争激烈时,线程上下文切换会带来较大开销。
  • 必须配对调用lockunlock 需一一对应,否则导致资源泄漏。

二、读写锁(Read-Write Lock)

1. 基本概念

  • 作用:允许多个读线程同时访问共享资源,但写线程独占资源,实现“读共享、写独占”。
  • 核心特性:读锁共享(SHARED)、写锁独占(EXCLUSIVE),适合读多写少场景。
  • 适用场景:配置文件读取、缓存数据访问(读操作远多于写操作)。

2. 关键 API

函数原型说明
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);初始化读写锁。
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);加读锁:允许其他读线程同时获取。
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);加写锁:阻塞所有读/写线程,直至锁释放。
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);解锁:释放读锁或写锁。

3. 示例代码

pthread_rwlock_t rwlock;
int config_data;

// 读线程
void* read_thread(void* arg) {
    pthread_rwlock_rdlock(&rwlock); // 加读锁
    // 读取 config_data...
    pthread_rwlock_unlock(&rwlock); // 释读锁
    return NULL;
}

// 写线程
void* write_thread(void* arg) {
    pthread_rwlock_wrlock(&rwlock); // 加写锁
    // 修改 config_data...
    pthread_rwlock_unlock(&rwlock); // 释写锁
    return NULL;
}

4. 注意事项

  • 写锁优先级:某些实现中,写锁可能饥饿读线程,需通过属性配置调整优先级。
  • 避免长时间持有写锁:写锁独占资源,长时间持有会降低并发性。
  • 锁类型匹配:读锁和写锁不能交叉释放(如对读锁调用 wrlock 会导致未定义行为)。

三、自旋锁(Spinlock)

1. 基本概念

  • 作用:获取锁时若锁被占用,线程通过循环(自旋)等待,而非进入睡眠。
  • 核心特性:非阻塞式加锁,适合锁持有时间极短的场景(避免上下文切换开销)。
  • 适用场景:内核态驱动开发、用户态高频短临界区(如无系统调用的纯计算逻辑)。

2. 关键 API

函数原型说明
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);初始化自旋锁,pshared 设为 PTHREAD_PROCESS_PRIVATE(进程内共享)。
int pthread_spin_lock(pthread_spinlock_t *lock);加锁:自旋等待直至获取锁。
int pthread_spin_trylock(pthread_spinlock_t *lock);尝试加锁:若锁可用则获取,否则返回 EBUSY(非阻塞)。
int pthread_spin_unlock(pthread_spinlock_t *lock);解锁:释放锁,唤醒自旋的线程(实际通过内存屏障通知)。

3. 示例代码

pthread_spinlock_t spinlock;
int counter = 0;

void* thread_func(void* arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_spin_lock(&spinlock); // 自旋加锁
        counter++;
        pthread_spin_unlock(&spinlock); // 解锁
    }
    return NULL;
}

int main() {
    pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE);
    // 创建线程...
    pthread_spin_destroy(&spinlock);
    return 0;
}

4. 注意事项

  • 忙等待开销:自旋期间占用 CPU 资源,锁持有时间长会导致 CPU 利用率下降。
  • 适用场景限制:仅适用于临界区执行时间极短(纳秒/微秒级)的场景。
  • 内存屏障:自旋锁通常依赖硬件原子操作和内存屏障保证数据一致性。

四、递归锁(Recursive Mutex)

1. 基本概念

  • 作用:允许同一线程多次获取同一把锁(普通互斥锁会导致死锁),用于递归函数或嵌套加锁场景。
  • 核心特性:记录锁的持有次数,同一线程多次加锁时计数器递增,解锁时递减,直至为零释放锁。
  • 适用场景:递归调用的函数、同一线程内多次加锁的逻辑(如类的成员函数多次调用自身)。

2. 关键 API

函数原型说明
初始化时设置属性:
pthread_mutexattr_t attr;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
通过属性将互斥锁设置为递归锁。

3. 示例代码

pthread_mutex_t recursive_mutex;

void recursive_function(int depth) {
    pthread_mutex_lock(&recursive_mutex); // 第一次加锁
    if (depth > 0) {
        recursive_function(depth - 1); // 递归调用时再次加锁(普通互斥锁会卡死)
    }
    pthread_mutex_unlock(&recursive_mutex); // 解锁(计数器递减)
}

int main() {
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 设置递归属性
    pthread_mutex_init(&recursive_mutex, &attr);
    // 调用递归函数...
    return 0;
}

4. 注意事项

  • 性能略低:因需维护锁的持有计数,开销比普通互斥锁稍高。
  • 避免滥用:仅在确实需要同一线程多次加锁时使用,普通场景优先用非递归锁。

五、条件变量(Condition Variable)

1. 基本概念

  • 作用:与互斥锁配合使用,实现线程间的同步(如等待某个条件满足后再执行)。
  • 核心特性:允许线程等待特定条件(如“数据准备就绪”“任务队列非空”),避免忙等待。
  • 适用场景:生产者-消费者模型、事件通知(一个线程通知多个线程执行)。

2. 关键 API

函数原型说明
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);初始化条件变量。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);等待条件:释放 mutex 并阻塞,条件满足时被唤醒并重新加锁。
int pthread_cond_signal(pthread_cond_t *cond);唤醒一个等待该条件的线程(单播)。
int pthread_cond_broadcast(pthread_cond_t *cond);唤醒所有等待该条件的线程(广播)。

3. 示例代码(生产者-消费者模型)

pthread_mutex_t mutex;
pthread_cond_t cond;
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 (count == 0) {
            pthread_cond_wait(&cond, &mutex); // 等待生产者通知
        }
        int data = buffer[--count];
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

4. 注意事项

  • 必须配合互斥锁pthread_cond_wait 需在持有锁的情况下调用,确保条件检查的原子性。
  • 虚假唤醒:即使没有 signal/broadcastcond_wait 也可能返回,需用 while 循环检查条件(而非 if)。
  • 广播与单播选择broadcast 用于唤醒所有等待线程(如资源重置),signal 用于唤醒单个线程(如任务就绪)。

六、屏障(Barrier)

1. 基本概念

  • 作用:同步多个线程,确保所有线程到达屏障点后再继续执行(类似“集合点”)。
  • 核心特性:等待指定数量的线程全部到达后,统一释放所有线程。
  • 适用场景:并行计算中的多阶段同步(如所有线程完成初始化后再进入计算阶段)。

2. 关键 API

函数原型说明
int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count);初始化屏障,count 为需等待的线程数。
int pthread_barrier_wait(pthread_barrier_t *barrier);线程到达屏障点,等待其他线程。若为最后一个到达的线程,唤醒所有线程。

3. 示例代码

pthread_barrier_t barrier;

void* thread_func(void* arg) {
    printf("Thread %d ready\n", *(int*)arg);
    pthread_barrier_wait(&barrier); // 等待所有线程就绪
    printf("Thread %d starts working\n", *(int*)arg);
    return NULL;
}

int main() {
    const int thread_count = 5;
    pthread_barrier_init(&barrier, NULL, thread_count);
    // 创建 5 个线程...
    pthread_barrier_destroy(&barrier);
    return 0;
}

4. 注意事项

  • 固定线程数:初始化时需指定确切的线程数,无法动态调整。
  • 错误处理pthread_barrier_wait 对最后一个到达的线程返回 PTHREAD_BARRIER_SERIAL_THREAD,其他线程返回 0,可用于区分主线程逻辑。

七、锁的对比与选择

锁类型互斥锁读写锁自旋锁递归锁条件变量屏障
加锁方式阻塞式读共享/写独占自旋等待可重入配合互斥锁集合点同步
适用场景通用互斥读多写少短临界区递归/嵌套加锁事件通知多线程同步
上下文切换无(忙等待)
典型场景共享变量配置文件高频小临界区递归函数生产者-消费者并行计算阶段
API 复杂度

八、最佳实践

  1. 减少锁持有时间:将非必要操作移到临界区外,降低锁竞争概率。
  2. 避免嵌套锁:确保持有锁的顺序一致(如按资源地址升序加锁),防止死锁。
  3. 选择合适的锁类型:读多写少用读写锁,短临界区用自旋锁,递归逻辑用递归锁。
  4. 错误处理:检查锁函数的返回值(如 pthread_mutex_lock 返回 EBUSY 表示加锁失败)。
  5. 性能测试:使用 perf 等工具分析锁竞争热点,优化临界区逻辑。

通过合理选择和使用锁机制,可以在保证线程安全的同时,最大限度提升程序的并发性能。实际开发中需结合具体场景,平衡安全性与效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值