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)机制实现线程同步。其核心原理包括:

  1. 独占访问:同一时刻仅允许一个线程持有锁
  2. 阻塞等待:未获得锁的线程会进入等待队列
  3. 原子操作:通过原子指令实现锁的获取和释放
  4. 死锁预防:通过加锁顺序、锁超时等机制避免死锁

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 内核中互斥锁的实现涉及以下关键机制:

  1. 原子操作:通过 atomic_read/atomic_set 等原子指令实现锁状态的读写
  2. 等待队列:当线程无法获取锁时,会加入等待队列
  3. 唤醒机制:当锁被释放时,通过 wake_up 唤醒等待队列中的线程
  4. 优先级继承:在某些实现中会处理优先级翻转问题

在用户空间,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. 性能优化方法

  1. 减少锁粒度:将大锁拆分为多个小锁
  2. 使用读写锁:适用于读多写少的场景
  3. 无锁数据结构:使用原子操作实现队列、栈等结构
  4. 锁分离:将锁拆分为读锁和写锁
  5. 锁超时机制:避免死锁和资源竞争

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. 常见错误

  1. 忘记解锁:导致死锁

    pthread_mutex_lock(&mutex);
    // 临界区代码
    // 未调用 pthread_mutex_unlock
  2. 在锁保护代码中发生异常:导致锁未释放

    pthread_mutex_lock(&mutex);
    try {
     // 可能抛出异常的代码
    } catch (...) {
     // 锁未释放
    }
  3. 递归锁使用不当:导致死锁

    pthread_mutex_lock(&mutex);
    pthread_mutex_lock(&mutex); // 递归锁需特殊初始化

2. 解决方法

  1. 使用 RAII 模式:通过智能指针自动管理锁
  2. 使用 trylock:尝试加锁避免阻塞
  3. 设置锁超时:防止死锁
  4. 按固定顺序加锁:避免死锁
  5. 使用锁计数器:处理递归锁

十、最佳实践

  1. 始终初始化和销毁锁:pthread_mutex_init/pthread_mutex_destroy
  2. 避免在锁保护代码中发生异常:使用 RAII 模式
  3. 合理选择锁类型:根据场景选择普通锁、递归锁或读写锁
  4. 减少锁粒度:将大锁拆分为多个小锁
  5. 结合条件变量:实现生产者-消费者模式等复杂同步
  6. 监控锁竞争:使用 perf 工具分析锁竞争情况
  7. 考虑无锁算法:在高并发场景中使用原子操作

十一、总结

线程互斥是多线程编程的核心技术,通过互斥锁机制可以有效解决数据竞争问题。本篇文章深入解析了互斥锁的工作原理、实现方式、常见错误及解决方案。实际应用中需根据具体场景选择合适的锁类型,通过合理设计减少锁粒度、避免死锁,并结合条件变量等机制实现复杂同步需求。在高并发场景中,可考虑使用无锁数据结构或更高级的同步机制。理解这些原理和实践方法,是构建稳定可靠的多线程系统的基础。

最后修改于:2026年09月24日 20:39

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日