在多线程编程中,当多个线程需要访问共享资源时,如果不加以控制,就会导致数据竞争(Data Race),引发程序行为不可预测。互斥锁(Mutex)是解决此类问题的核心同步机制之一。
1. 核心概念
1.1 临界资源(Critical Resource)
一次只允许一个线程访问的共享资源。例如:全局变量、静态变量、堆内存、文件、硬件设备等。
1.2 临界区(Critical Section)
访问临界资源的那段代码。线程进入临界区前必须获得锁,离开临界区后必须释放锁。
1.3 互斥机制(Mutual Exclusion)
通过互斥锁(Mutex)实现。线程在进入临界区前申请锁(加锁),如果锁已被其他线程持有,则当前线程会被阻塞;访问完临界资源后释放锁(解锁),允许其他线程获取。
2. 互斥锁的初始化与销毁
POSIX 线程库(pthread)提供了互斥锁的操作接口。使用前需包含头文件 <pthread.h>。
2.1 动态初始化:pthread_mutex_init
c
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数说明:
mutex:指向要初始化的互斥锁对象。attr:互斥锁属性指针。通常传入NULL表示使用缺省属性(快速互斥锁)。
返回值:
- 成功时返回
0。 - 失败时返回错误码(非零),可通过
strerror(errno)查看错误信息。
示例:
c
pthread_mutex_t mutex;
if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("pthread_mutex_init failed");
exit(EXIT_FAILURE);
}
2.2 静态初始化:PTHREAD_MUTEX_INITIALIZER
对于全局或静态的互斥锁,可以使用宏进行静态初始化,无需调用 pthread_mutex_init。
c
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2.3 销毁互斥锁:pthread_mutex_destroy
c
int pthread_mutex_destroy(pthread_mutex_t *mutex);
注意事项:
- 只能销毁已初始化且未被锁定的互斥锁。
- 在 Linux 中,互斥锁本身不占用系统资源(如文件描述符),因此
pthread_mutex_destroy的主要作用是检查锁状态(如果锁处于锁定状态,则返回EBUSY)并使其不可用。 - 销毁后的互斥锁不可再使用,除非重新初始化。
3. 互斥锁的基本使用
3.1 加锁:pthread_mutex_lock
c
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 如果互斥锁未被锁定,则调用线程将其锁定并立即返回。
- 如果互斥锁已被其他线程锁定,则调用线程将被阻塞,直到锁被释放。
3.2 解锁:pthread_mutex_unlock
c
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 释放由调用线程锁定的互斥锁。
- 如果其他线程正在等待该锁,其中一个将被唤醒并获得锁。
3.3 尝试加锁:pthread_mutex_trylock
c
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 尝试锁定互斥锁。
- 如果锁可用,则锁定并返回
0。 - 如果锁已被其他线程锁定,则立即返回错误码
EBUSY,而不会阻塞调用线程。
4. 完整使用示例
下面是一个简单的多线程计数器示例,演示如何使用互斥锁保护共享变量。
c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define THREAD_NUM 5
#define COUNT_LIMIT 10000
int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* increment(void* arg) {
for (int i = 0; i < COUNT_LIMIT; ++i) {
pthread_mutex_lock(&mutex); // 进入临界区前加锁
counter++;
pthread_mutex_unlock(&mutex); // 离开临界区后解锁
}
return NULL;
}
int main() {
pthread_t threads[THREAD_NUM];
// 创建多个线程同时增加计数器
for (int i = 0; i < THREAD_NUM; ++i) {
pthread_create(&threads[i], NULL, increment, NULL);
}
// 等待所有线程结束
for (int i = 0; i < THREAD_NUM; ++i) {
pthread_join(threads[i], NULL);
}
printf("Final counter value: %d (expected: %d)\n", counter, THREAD_NUM * COUNT_LIMIT);
return 0;
}
输出结果:
Final counter value: 50000 (expected: 50000)
如果没有互斥锁保护,多个线程同时执行 counter++(该操作非原子)会导致最终结果小于预期。