Linux--线程的互斥
'# Linux--线程的互斥
一、背景与问题
在多线程编程中,线程间共享的资源(如全局变量、文件句柄、内存缓冲区等)可能因并发访问导致数据不一致或竞态条件。例如:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int shared_data = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_func(void* arg) {
for (int i = 0; i < 100000; ++i) {
pthread_mutex_lock(&mutex);
shared_data++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final value: %d\n", shared_data);
return 0;
}运行结果可能为 199998 或 200000,这说明多个线程同时修改共享变量时会导致数据不一致。这种问题本质上是线程间对共享资源的并发访问导致的。
二、基本原理
Linux 系统通过互斥锁(mutex)机制实现线程同步。其核心原理包括:
- 独占访问:同一时刻仅允许一个线程持有锁
- 阻塞等待:未获得锁的线程会进入等待队列
- 原子操作:通过原子指令实现锁的获取和释放
- 死锁预防:通过加锁顺序、锁超时等机制避免死锁
POSIX 线程库提供了以下关键函数:
pthread_mutex_init:初始化互斥锁pthread_mutex_lock:获取锁(阻塞式)pthread_mutex_unlock:释放锁pthread_mutex_destroy:销毁锁
三、环境准备
在 Linux 系统中,需确保安装了开发工具链:
sudo apt-get update
sudo apt-get install build-essential编译时需链接 pthread 库:
gcc -o mutex_example mutex_example.c -lpthread四、核心实现
1. 基础互斥锁使用
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 定义全局变量和互斥锁
int shared_data = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 线程函数
void* thread_func(void* arg) {
for (int i = 0; i < 100000; ++i) {
pthread_mutex_lock(&mutex); // 获取锁
shared_data++;
pthread_mutex_unlock(&mutex); // 释放锁
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final value: %d\n", shared_data);
return 0;
}关键代码解释:
pthread_mutex_lock会阻塞直到获取锁,确保临界区代码独占执行pthread_mutex_unlock释放锁,允许其他线程获取锁- 这种方式能保证
shared_data++操作的原子性
2. 递归锁(Recursive Mutex)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t recursive_mutex = PTHREAD_MUTEX_RECURSIVE_INITIALIZER_NP;
void* thread_func(void* arg) {
pthread_mutex_lock(&recursive_mutex);
printf("Locked by thread %lu\n", pthread_self());
pthread_mutex_unlock(&recursive_mutex);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}关键点:
- 递归锁允许同一线程多次加锁
- 适用于需要在函数内部多次加锁的场景
- 但可能导致更高的上下文切换开销
3. 带超时的锁尝试
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t timeout_mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_func(void* arg) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += 100000000; // 100ms
pthread_mutex_lock(&timeout_mutex);
printf("Locked by thread %lu\n", pthread_self());
pthread_mutex_unlock(&timeout_mutex);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}关键点:
pthread_mutex_lock可带超时参数- 适用于需要处理锁竞争的场景
- 但需要处理
ETIMEDOUT错误码
五、完整案例
银行账户转账系统
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
typedef struct {
char* name;
int balance;
pthread_mutex_t lock;
} Account;
void transfer(Account* from, Account* to, int amount) {
pthread_mutex_lock(&from->lock);
pthread_mutex_lock(&to->lock);
if (from->balance >= amount) {
from->balance -= amount;
to->balance += amount;
printf("Transfer %d from %s to %s\n", amount, from->name, to->name);
} else {
printf("Insufficient balance in %s\n", from->name);
}
pthread_mutex_unlock(&to->lock);
pthread_mutex_unlock(&from->lock);
}
void* thread_func(void* arg) {
Account* acc = (Account*)arg;
for (int i = 0; i < 10; ++i) {
transfer(acc, acc, 100); // 自己转自己
usleep(100000);
}
return NULL;
}
int main() {
Account* a = malloc(sizeof(Account));
Account* b = malloc(sizeof(Account));
a->name = "A";
b->name = "B";
a->balance = 1000;
b->balance = 1000;
pthread_mutex_init(&a->lock, NULL);
pthread_mutex_init(&b->lock, NULL);
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, a);
pthread_create(&t2, NULL, thread_func, b);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final balances:\n");
printf("A: %d, B: %d\n", a->balance, b->balance);
free(a);
free(b);
return 0;
}关键点分析:
- 使用两个互斥锁保护账户数据
- 在转账函数中同时加锁两个账户
- 避免了死锁(按固定顺序加锁)
- 保证了转账操作的原子性
六、源码解析
Linux 内核中互斥锁的实现涉及以下关键机制:
- 原子操作:通过
atomic_read/atomic_set等原子指令实现锁状态的读写 - 等待队列:当线程无法获取锁时,会加入等待队列
- 唤醒机制:当锁被释放时,通过
wake_up唤醒等待队列中的线程 - 优先级继承:在某些实现中会处理优先级翻转问题
在用户空间,POSIX 线程库通过以下方式实现互斥锁:
// 简化的 pthread_mutex_lock 实现
int pthread_mutex_lock(pthread_mutex_t* mutex) {
if (atomic_read(&mutex->lock) == 0) {
atomic_set(&mutex->lock, 1);
return 0;
} else {
// 加入等待队列并阻塞
wait_for_lock(mutex);
}
}七、进阶使用
1. 读写锁(Read-Write Lock)
适用于读多写少的场景:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void* reader_func(void* arg) {
pthread_rwlock_rdlock(&rwlock);
printf("Reader %lu: Reading\n", pthread_self());
pthread_rwlock_unlock(&rwlock);
return NULL;
}
void* writer_func(void* arg) {
pthread_rwlock_wrlock(&rwlock);
printf("Writer %lu: Writing\n", pthread_self());
pthread_rwlock_unlock(&rwlock);
return NULL;
}
int main() {
pthread_t r1, r2, w1;
pthread_create(&r1, NULL, reader_func, NULL);
pthread_create(&r2, NULL, reader_func, NULL);
pthread_create(&w1, NULL, writer_func, NULL);
pthread_join(r1, NULL);
pthread_join(r2, NULL);
pthread_join(w1, NULL);
return 0;
}2. 条件变量(Condition Variable)
用于实现生产者-消费者模式:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
for (int i = 0; i < 10; ++i) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE) {
pthread_cond_wait(&cond, &mutex);
}
buffer[count++] = i;
printf("Produced: %d\n", i);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
usleep(100000);
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 10; ++i) {
pthread_mutex_lock(&mutex);
while (count == 0) {
pthread_cond_wait(&cond, &mutex);
}
printf("Consumed: %d\n", buffer[--count]);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
usleep(100000);
}
return NULL;
}
int main() {
pthread_t p, c;
pthread_create(&p, NULL, producer, NULL);
pthread_create(&c, NULL, consumer, NULL);
pthread_join(p, NULL);
pthread_join(c, NULL);
return 0;
}八、性能与工程实践
1. 性能优化方法
- 减少锁粒度:将大锁拆分为多个小锁
- 使用读写锁:适用于读多写少的场景
- 无锁数据结构:使用原子操作实现队列、栈等结构
- 锁分离:将锁拆分为读锁和写锁
- 锁超时机制:避免死锁和资源竞争
2. 异常安全处理
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
class MutexGuard {
public:
MutexGuard(pthread_mutex_t* m) : mutex(m) {
pthread_mutex_lock(mutex);
}
~MutexGuard() {
pthread_mutex_unlock(mutex);
}
private:
pthread_mutex_t* mutex;
};
void* thread_func(void* arg) {
MutexGuard guard(&mutex);
// 临界区代码
return NULL;
}3. 安全风险分析
- 资源泄漏:未调用
pthread_mutex_destroy - 死锁:加锁顺序不一致
- 竞态条件:未保护共享资源
- 优先级翻转:实时系统中可能影响任务调度
九、常见问题与踩坑
1. 常见错误
忘记解锁:导致死锁
pthread_mutex_lock(&mutex); // 临界区代码 // 未调用 pthread_mutex_unlock在锁保护代码中发生异常:导致锁未释放
pthread_mutex_lock(&mutex); try { // 可能抛出异常的代码 } catch (...) { // 锁未释放 }递归锁使用不当:导致死锁
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex); // 递归锁需特殊初始化
2. 解决方法
- 使用 RAII 模式:通过智能指针自动管理锁
- 使用 trylock:尝试加锁避免阻塞
- 设置锁超时:防止死锁
- 按固定顺序加锁:避免死锁
- 使用锁计数器:处理递归锁
十、最佳实践
- 始终初始化和销毁锁:
pthread_mutex_init/pthread_mutex_destroy - 避免在锁保护代码中发生异常:使用 RAII 模式
- 合理选择锁类型:根据场景选择普通锁、递归锁或读写锁
- 减少锁粒度:将大锁拆分为多个小锁
- 结合条件变量:实现生产者-消费者模式等复杂同步
- 监控锁竞争:使用
perf工具分析锁竞争情况 - 考虑无锁算法:在高并发场景中使用原子操作
十一、总结
线程互斥是多线程编程的核心技术,通过互斥锁机制可以有效解决数据竞争问题。本篇文章深入解析了互斥锁的工作原理、实现方式、常见错误及解决方案。实际应用中需根据具体场景选择合适的锁类型,通过合理设计减少锁粒度、避免死锁,并结合条件变量等机制实现复杂同步需求。在高并发场景中,可考虑使用无锁数据结构或更高级的同步机制。理解这些原理和实践方法,是构建稳定可靠的多线程系统的基础。
评论已关闭