# 互斥锁 (Mutex) ## 为什么需要互斥锁 ### 竞态条件 (Race Condition) 多个线程同时访问共享资源,最终结果依赖于线程的执行顺序。 ### 数据竞争 (Data Competition) 两个或多个线程同时访问同一个变量,且至少有一个线程在写入,而没有同步机制。 **示例:无同步的计数器** ```c #include #include 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 基本操作 ### 初始化与销毁 ```c #include // 动态初始化 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); ``` **注意事项**: - 已初始化的互斥锁不能重复初始化 - 销毁正在被锁的互斥锁是未定义行为 - 静态初始化的互斥锁不需要销毁 ### 加锁与解锁 ```c // 阻塞加锁 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) ```c 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. 锁顺序不一致 ```c // 线程1 pthread_mutex_lock(&lockA); pthread_mutex_lock(&lockB); // 可能死锁 // 线程2 pthread_mutex_lock(&lockB); pthread_mutex_lock(&lockA); // 可能死锁 ``` **解决方案**:统一锁顺序 ```c // 所有线程都按此顺序 pthread_mutex_lock(&lockA); pthread_mutex_lock(&lockB); ``` #### 2. 信号处理函数中加锁 ```c 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; } ``` **问题**:如果信号在临界区中发生,会导致死锁。 **解决方案**: - 使用 `sigaction` 的 `SA_RESTART` 选项 - 在信号处理函数中使用非阻塞锁 (`trylock`) - 使用信号屏蔽 --- ## 生产者-消费者模型基础 ### 简单版本(互斥锁+计数器) ```c #include #include #include #include #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; } ``` **编译命令**: ```bash gcc -o simple_pc simple_pc.c -pthread ``` --- ## 代码示例:线程安全计数器 ```c #include #include #include 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; } ``` **编译命令**: ```bash 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. 线程基础]]