10. 互斥锁.md 7.9 KB

互斥锁 (Mutex)

为什么需要互斥锁

竞态条件 (Race Condition)

多个线程同时访问共享资源,最终结果依赖于线程的执行顺序。

数据竞争 (Data Competition)

两个或多个线程同时访问同一个变量,且至少有一个线程在写入,而没有同步机制。

示例:无同步的计数器

#include <stdio.h>
#include <pthread.h>

int counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 1000000; i++) {
        counter++; // 非原子操作:读-改-写
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("Expected: 2000000, Actual: %d\n", counter);
    return 0;
}

问题:输出不确定,可能小于2000000。


pthread_mutex_t 基本操作

初始化与销毁

#include <pthread.h>

// 动态初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

// 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 销毁
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);

返回值

  • 成功:0
  • 失败:错误码(EINVAL, EDEADLK, EBUSY等)

非阻塞加锁 (trylock)

void* worker(void* arg) {
    pthread_mutex_t* mutex = (pthread_mutex_t*)arg;

    if (pthread_mutex_trylock(mutex) == 0) {
        // 成功获得锁
        printf("Thread %lu got the lock\n", pthread_self());
        // 临界区操作
        pthread_mutex_unlock(mutex);
    } else {
        // 无法获得锁,执行其他任务
        printf("Thread %lu couldn't get lock\n", pthread_self());
    }
    return NULL;
}

死锁 (Deadlock)

四个必要条件

  1. 互斥条件:资源一次只能由一个线程持有
  2. 持有并等待:线程持有至少一个资源,同时等待获取其他资源
  3. 非抢占条件:已分配的资源不能被强制剥夺
  4. 循环等待条件:存在线程的循环等待链

死锁防范策略

  1. 锁顺序策略:所有线程以相同顺序获取锁
  2. 超时机制:使用 pthread_mutex_timedlock
  3. 死锁检测:使用工具或设计检测机制
  4. 资源分配图:分析资源依赖关系

嵌入式死锁场景

1. 锁顺序不一致

// 线程1
pthread_mutex_lock(&lockA);
pthread_mutex_lock(&lockB);  // 可能死锁

// 线程2
pthread_mutex_lock(&lockB);
pthread_mutex_lock(&lockA);  // 可能死锁

解决方案:统一锁顺序

// 所有线程都按此顺序
pthread_mutex_lock(&lockA);
pthread_mutex_lock(&lockB);

2. 信号处理函数中加锁

pthread_mutex_t mutex;

void signal_handler(int signum) {
    pthread_mutex_lock(&mutex);  // 危险!
    // 处理信号
    pthread_mutex_unlock(&mutex);
}

void* worker(void* arg) {
    pthread_mutex_lock(&mutex);
    // 长时间操作
    pthread_mutex_unlock(&mutex);
    return NULL;
}

问题:如果信号在临界区中发生,会导致死锁。

解决方案

  • 使用 sigactionSA_RESTART 选项
  • 在信号处理函数中使用非阻塞锁 (trylock)
  • 使用信号屏蔽

生产者-消费者模型基础

简单版本(互斥锁+计数器)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define BUFFER_SIZE 10

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int buffer = 0;
int count = 0;  // 缓冲区中的物品数量

void* producer(void* arg) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&mutex);

        if (count < BUFFER_SIZE) {
            buffer++;
            count++;
            printf("Producer: produced item %d, total: %d\n", i, count);
        } else {
            printf("Producer: buffer full, waiting...\n");
        }

        pthread_mutex_unlock(&mutex);
        usleep(100000);  // 100ms
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&mutex);

        if (count > 0) {
            buffer--;
            count--;
            printf("Consumer: consumed item %d, total: %d\n", i, count);
        } else {
            printf("Consumer: buffer empty, waiting...\n");
        }

        pthread_mutex_unlock(&mutex);
        usleep(150000);  // 150ms
    }
    return NULL;
}

int main() {
    pthread_t prod_thread, cons_thread;

    pthread_create(&prod_thread, NULL, producer, NULL);
    pthread_create(&cons_thread, NULL, consumer, NULL);

    pthread_join(prod_thread, NULL);
    pthread_join(cons_thread, NULL);

    pthread_mutex_destroy(&mutex);
    return 0;
}

编译命令

gcc -o simple_pc simple_pc.c -pthread

代码示例:线程安全计数器

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct {
    int count;
    pthread_mutex_t mutex;
} ThreadSafeCounter;

void counter_init(ThreadSafeCounter* counter, int initial) {
    counter->count = initial;
    pthread_mutex_init(&counter->mutex, NULL);
}

void counter_destroy(ThreadSafeCounter* counter) {
    pthread_mutex_destroy(&counter->mutex);
}

void counter_increment(ThreadSafeCounter* counter) {
    pthread_mutex_lock(&counter->mutex);
    counter->count++;
    pthread_mutex_unlock(&counter->mutex);
}

void counter_decrement(ThreadSafeCounter* counter) {
    pthread_mutex_lock(&counter->mutex);
    counter->count--;
    pthread_mutex_unlock(&counter->mutex);
}

int counter_get(ThreadSafeCounter* counter) {
    pthread_mutex_lock(&counter->mutex);
    int value = counter->count;
    pthread_mutex_unlock(&counter->mutex);
    return value;
}

void* increment_worker(void* arg) {
    ThreadSafeCounter* counter = (ThreadSafeCounter*)arg;
    for (int i = 0; i < 1000000; i++) {
        counter_increment(counter);
    }
    return NULL;
}

int main() {
    ThreadSafeCounter counter;
    counter_init(&counter, 0);

    pthread_t t1, t2;
    pthread_create(&t1, NULL, increment_worker, &counter);
    pthread_create(&t2, NULL, increment_worker, &counter);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("Expected: 2000000, Actual: %d\n", counter_get(&counter));

    counter_destroy(&counter);
    return 0;
}

编译命令

gcc -o thread_safe_counter thread_safe_counter.c -pthread

注意事项

  1. 锁粒度:锁太粗会降低并发性,锁太细增加开销
  2. 异常处理:确保所有路径都能正确释放锁
  3. 性能影响:互斥锁会减少并发性,合理使用
  4. 初始化时机:在使用前初始化,避免重复初始化
  5. 销毁时机:确保没有线程在使用时销毁

面试要点

Q1: 互斥锁和自旋锁的区别?

A:

  • 互斥锁:线程无法获得锁时会睡眠,适用于临界区较长的情况
  • 自旋锁:线程无法获得锁时会忙等,适用于临界区很短的情况

Q2: 如何避免死锁?

A:

  1. 统一锁顺序
  2. 使用超时机制
  3. 避免嵌套锁
  4. 锁粒度最小化

Q3: pthread_mutex_trylock 的使用场景?

A:

  1. 需要非阻塞操作
  2. 尝试获取锁失败时执行备用逻辑
  3. 避免死锁

Q4: 互斥锁的类型有哪些?

A:

  1. PTHREAD_MUTEX_NORMAL:普通互斥锁
  2. PTHREAD_MUTEX_ERRORCHECK:错误检查锁
  3. PTHREAD_MUTEX_RECURSIVE:递归锁
  4. PTHREAD_MUTEX_DEFAULT:默认类型

Q5: 递归锁的使用场景?

A:

  1. 函数内部需要调用其他也需要锁的函数
  2. 递归数据结构
  3. 但应尽量避免使用递归锁

相关链接

  • [[11. 条件变量]]
  • [[8. 线程基础]]