插件窝 干货文章 详解Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量

详解Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量

pthread 信号量 Mutex 互斥 578    来源:    2025-05-13

Linux多线程同步机制详解

在Linux多线程编程中,为了保证线程安全和数据一致性,我们需要使用各种同步机制。下面我将详细介绍五种主要的同步机制:互斥锁、读写锁、自旋锁、条件变量和信号量。

1. 互斥锁(Mutex)

基本概念

互斥锁(Mutual Exclusion Lock)是最基本的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。

主要特点

  • 阻塞机制:获取不到锁的线程会进入睡眠状态
  • 适用于临界区执行时间较长的场景
  • 不可递归使用(除非明确指定为递归锁)

常用API

#include <pthread.h>

// 初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 尝试加锁(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);

// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

示例代码

pthread_mutex_t mutex;

void* thread_func(void* arg) {
    pthread_mutex_lock(&mutex);
    // 临界区代码
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_mutex_init(&mutex, NULL);
    pthread_t thread;
    pthread_create(&thread, NULL, thread_func, NULL);
    // ...
    pthread_mutex_destroy(&mutex);
    return 0;
}

2. 读写锁(Read-Write Lock)

基本概念

读写锁允许多个线程同时读取共享资源,但写操作需要独占访问。适用于读多写少的场景。

主要特点

  • 读模式:多个线程可以同时获取读锁
  • 写模式:只能有一个线程获取写锁,且获取写锁时不能有其他读锁或写锁
  • 写锁优先(大多数实现)

常用API

#include <pthread.h>

// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

// 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

// 获取读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

// 尝试获取读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

// 获取写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

// 尝试获取写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

// 释放读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

示例代码

pthread_rwlock_t rwlock;
int shared_data = 0;

void* reader(void* arg) {
    pthread_rwlock_rdlock(&rwlock);
    printf("Reader: %d\n", shared_data);
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}

void* writer(void* arg) {
    pthread_rwlock_wrlock(&rwlock);
    shared_data++;
    printf("Writer: %d\n", shared_data);
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}

3. 自旋锁(Spinlock)

基本概念

自旋锁与互斥锁类似,但在获取不到锁时不会使线程睡眠,而是不断循环检查锁状态(自旋)。

主要特点

  • 非阻塞机制:获取不到锁时忙等待
  • 适用于临界区执行时间非常短的场景
  • 在用户空间使用较少,更多用于内核
  • 会消耗CPU资源

常用API

#include <pthread.h>

// 初始化自旋锁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

// 销毁自旋锁
int pthread_spin_destroy(pthread_spinlock_t *lock);

// 加锁
int pthread_spin_lock(pthread_spinlock_t *lock);

// 尝试加锁
int pthread_spin_trylock(pthread_spinlock_t *lock);

// 解锁
int pthread_spin_unlock(pthread_spinlock_t *lock);

示例代码

pthread_spinlock_t spinlock;

void* thread_func(void* arg) {
    pthread_spin_lock(&spinlock);
    // 非常短的临界区代码
    pthread_spin_unlock(&spinlock);
    return NULL;
}

4. 条件变量(Condition Variable)

基本概念

条件变量用于线程间的条件等待,通常与互斥锁配合使用,允许线程在某个条件不满足时挂起等待。

主要特点

  • 必须与互斥锁一起使用
  • 提供了一种线程间通知机制
  • 避免忙等待,节省CPU资源

常用API

#include <pthread.h>

// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

// 等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

// 带超时的等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, 
                          const struct timespec *abstime);

// 唤醒一个等待线程
int pthread_cond_signal(pthread_cond_t *cond);

// 唤醒所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);

示例代码

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void* producer(void* arg) {
    pthread_mutex_lock(&mutex);
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* consumer(void* arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {
        pthread_cond_wait(&cond, &mutex);
    }
    // 处理数据
    pthread_mutex_unlock(&mutex);
    return NULL;
}

5. 信号量(Semaphore)

基本概念

信号量是一种计数器,用于控制对共享资源的访问数量。可以是二值信号量(类似互斥锁)或计数信号量。

主要特点

  • 可以跨进程使用(命名信号量)
  • 有POSIX信号量和System V信号量两种实现
  • 比互斥锁更灵活,可以允许多个线程同时访问资源

常用API (POSIX信号量)

#include <semaphore.h>

// 初始化无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);

// 销毁信号量
int sem_destroy(sem_t *sem);

// 等待信号量(P操作)
int sem_wait(sem_t *sem);

// 非阻塞等待
int sem_trywait(sem_t *sem);

// 带超时的等待
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

// 释放信号量(V操作)
int sem_post(sem_t *sem);

// 获取信号量当前值
int sem_getvalue(sem_t *sem, int *sval);

示例代码

sem_t sem;

void* thread_func(void* arg) {
    sem_wait(&sem);
    // 临界区代码
    sem_post(&sem);
    return NULL;
}

int main() {
    sem_init(&sem, 0, 1); // 二值信号量
    pthread_t thread;
    pthread_create(&thread, NULL, thread_func, NULL);
    // ...
    sem_destroy(&sem);
    return 0;
}

对比总结

同步机制 适用场景 特点 性能影响
互斥锁 一般临界区保护 简单易用,线程阻塞 上下文切换开销
读写锁 读多写少场景 读并发高,写独占 比互斥锁复杂
自旋锁 极短临界区 忙等待,不睡眠 消耗CPU但无上下文切换
条件变量 条件等待 必须配合互斥锁 高效等待
信号量 资源计数控制 灵活,可跨进程 比互斥锁稍重

选择建议

  1. 互斥锁:大多数情况下首选,简单可靠
  2. 读写锁:当确实存在大量读操作和少量写操作时
  3. 自旋锁:仅在确定临界区极短且不涉及系统调用时
  4. 条件变量:当需要等待特定条件满足时
  5. 信号量:需要控制资源数量或跨进程同步时

正确使用这些同步机制可以有效避免多线程程序中的竞态条件、死锁等问题,保证程序的正确性和性能。