在多线程编程环境中,当多个线程需要访问和操作共享资源(如全局变量、共享内存等)时,就可能发生“竞态条件”,为了避免数据混乱和不一致,Linux/POSIX 提供了互斥锁作为线程同步的重要工具,互斥锁本质上是一种“互斥”机制,它确保在任意时刻,只有一个线程能够进入被保护的代码区域,通常称为“临界区”。
互斥锁的生命周期
使用一个互斥锁通常遵循固定的生命周期:创建与初始化、加锁、访问共享资源、解锁,最后销毁。
- 初始化:在使用前,必须先初始化一个互斥锁,可以静态初始化,也可以使用
pthread_mutex_init
函数动态初始化。 - 加锁:线程在进入临界区之前,尝试获取互斥锁的所有权,如果锁未被占用,则该线程获得锁并继续执行;如果锁已被其他线程持有,则该线程会阻塞,直到锁被释放。
- 解锁:当线程离开临界区时,必须释放互斥锁,以便其他等待的线程可以获取它。
- 销毁:当互斥锁不再需要时,应销毁它以释放系统资源。
核心函数详解
POSIX 线程库(pthread)提供了一系列函数来操作互斥锁,下表总结了最核心的几个函数:
函数 | 功能 | 参数 | 备注 |
---|---|---|---|
pthread_mutex_init |
初始化一个互斥锁 | pthread_mutex_t *mutex , const pthread_mutexattr_t *attr |
attr 通常设为 NULL ,表示使用默认属性,成功返回0。 |
pthread_mutex_lock |
阻塞式加锁 | pthread_mutex_t *mutex |
如果锁已被占用,调用线程将一直阻塞,直到获得锁。 |
pthread_mutex_trylock |
非阻塞式加锁 | pthread_mutex_t *mutex |
如果锁可用,则获取;否则立即返回错误码 EBUSY ,不会阻塞。 |
pthread_mutex_unlock |
解锁 | pthread_mutex_t *mutex |
释放锁,允许其他等待的线程获取,必须由持有锁的线程调用。 |
pthread_mutex_destroy |
销毁互斥锁 | pthread_mutex_t *mutex |
释放互斥锁占用的资源,销毁后不可再使用。 |
示例代码
下面的例子演示了如何使用互斥锁保护一个共享的计数器,若无保护,最终结果很可能小于预期值;加上互斥锁后,结果将完全正确。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define NUM_THREADS 2 #define ITERATIONS 1000000 int shared_counter = 0; pthread_mutex_t counter_mutex; void* increment_counter(void* arg) { for (int i = 0; i < ITERATIONS; ++i) { // 加锁,保护临界区 pthread_mutex_lock(&counter_mutex); // --- 临界区开始 --- shared_counter++; // --- 临界区结束 --- // 解锁 pthread_mutex_unlock(&counter_mutex); } return NULL; } int main() { pthread_t threads[NUM_THREADS]; // 初始化互斥锁 if (pthread_mutex_init(&counter_mutex, NULL) != 0) { perror("Failed to initialize mutex"); return 1; } // 创建线程 for (int i = 0; i < NUM_THREADS; ++i) { if (pthread_create(&threads[i], NULL, increment_counter, NULL) != 0) { perror("Failed to create thread"); return 1; } } // 等待所有线程完成 for (int i = 0; i < NUM_THREADS; ++i) { pthread_join(threads[i], NULL); } printf("Expected counter value: %d\n", NUM_THREADS * ITERATIONS); printf("Actual counter value: %d\n", shared_counter); // 销毁互斥锁 pthread_mutex_destroy(&counter_mutex); return 0; }
注意事项与最佳实践
- 避免死锁:死锁是最常见的错误,例如一个线程获取了锁后忘记释放,或者在持有锁的过程中调用了可能导致阻塞的函数,务必确保
lock
和unlock
成对出现,即使在if
或错误处理路径中也要如此。 - 减小锁的粒度:临界区应尽可能短小,只在确实需要保护共享资源时才持有锁,长时间占用锁会严重降低程序的并发性能。
- 选择合适的锁类型:通过
pthread_mutexattr_t
可以设置互斥锁的属性,例如设置成检错锁(PTHREAD_MUTEX_ERRORCHECK
)或可重入锁(PTHREAD_MUTEX_RECURSIVE
),以适应不同的场景需求。
互斥锁是构建线程安全、稳定可靠的Linux应用程序的基石,正确理解并熟练使用它,是每一位多线程开发者的必备技能。