Linux线程互斥锁
1. 什么是互斥锁(Mutex)?
互斥锁(Mutex,Mutual Exclusion) 是一种用于多线程编程的同步机制,用于保护共享资源(如变量、内存、文件等),确保在同一时刻只有一个线程可以访问共享资源。互斥锁的目的是防止多个线程同时访问共享资源,避免由于并发操作引发的数据竞争(race condition)和不一致的结果。
线程同步问题:在多线程环境中,多个线程可能同时访问或修改共享数据。没有适当的同步机制时,程序的行为可能变得不可预测。为了防止这种情况,互斥锁用于控制对共享资源的访问,保证同一时间只有一个线程可以访问资源。
2. 没有使用互斥锁的错误示例
我们先看一个没有使用互斥锁的简单例子,展示在多线程中如何出现数据竞争。
没有互斥锁导致的加减叠加问题:
假设我们有一个共享的全局变量 counter
,多个线程同时对它进行加法或减法操作,导致结果错误。
#include <stdio.h>
#include <pthread.h>
int counter = 0; // 共享变量
// 增加线程函数
void* increment(void* arg) {for (int i = 0; i < 1000000; i++) {counter++; // 增加操作}return NULL;
}
// 减少线程函数
void* decrement(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, decrement, NULL);// 等待两个线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 打印最终结果printf("Final counter value: %d\n", counter);return 0;
}
//输出的结果是随机的
在这个程序中,我们创建了两个线程:
-
increment
线程不断地增加counter
的值。 -
decrement
线程不断地减少counter
的值。
由于这两个线程在修改 counter
时没有同步机制,它们可能会同时访问和修改 counter
变量。例如:
-
线程 A(
increment
)读取counter
的值(假设是 0),然后增加 1。 -
线程 B(
decrement
)也读取counter
的值(假设也是 0),然后减少 1。
由于没有同步,两者的修改会“互相覆盖”,导致最终的结果与预期不符。这种情况被称为 数据竞争(race condition)。
3. 使用互斥锁解决问题
为了解决上面的问题,我们可以使用 互斥锁(mutex) 来确保每次只有一个线程可以访问和修改 counter
变量。
使用互斥锁的正确流程
加入互斥锁后的程序:
#include <stdio.h>
#include <pthread.h>
int counter = 0; // 共享变量
pthread_mutex_t lock; // 互斥锁
// 增加线程函数
void* increment(void* arg) {for (int i = 0; i < 1000000; i++) {pthread_mutex_lock(&lock); // 获取锁counter++; // 增加操作pthread_mutex_unlock(&lock); // 释放锁}return NULL;
}
// 减少线程函数
void* decrement(void* arg) {pthread_mutex_lock(&lock); // 获取锁for (int i = 0; i < 1000000; i++) {counter--; // 减少操作}pthread_mutex_unlock(&lock); // 释放锁//这两种方式都可以return NULL;
}
int main() {pthread_t t1, t2;
// 初始化互斥锁pthread_mutex_init(&lock, NULL);// 创建两个线程pthread_create(&t1, NULL, increment, NULL);pthread_create(&t2, NULL, decrement, NULL);// 等待两个线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 打印最终结果printf("Final counter value: %d\n", counter);// 销毁互斥锁pthread_mutex_destroy(&lock);return 0;
}
解释:
-
互斥锁的使用:
-
在
increment
和decrement
线程的关键部分,分别用pthread_mutex_lock(&lock)
来获取锁,用pthread_mutex_unlock(&lock)
来释放锁。 -
只有在一个线程获得锁后,另一个线程才能访问共享资源
counter
,从而避免并发冲突。
-
-
pthread_mutex_init(&lock, NULL)
:-
初始化互斥锁。在使用互斥锁之前,需要先进行初始化。
-
-
pthread_mutex_destroy(&lock)
:-
在程序结束时销毁互斥锁,释放资源。
-
4. 程序输出:
运行上面的程序时,两个线程安全地访问和修改 counter
变量。由于互斥锁的保护,程序将输出:
Final counter value: 0
这个结果是正确的,因为两个线程分别对 counter
进行了相同数量的加法和减法操作,互斥锁确保了操作的互斥性,避免了数据竞争。
最终的流程应该是如下