当前位置: 首页 > news >正文

Linux线程安全

Linux线程安全

  • Linux线程安全详解
    • Linux线程互斥
      • 进程线程间的互斥相关背景概念
      • 互斥量mutex
      • 互斥量的接口
      • 互斥量实现原理探究
    • 可重入VS线程安全
      • 概念
      • 常见的线程不安全的情况
      • 常见的线程安全的情况
      • 常见的不可重入的情况
      • 常见的可重入的情况
      • 可重入与线程安全联系
      • 可重入与线程安全区别
    • 常见锁概念
      • 死锁
      • 死锁的四个必要条件
      • 避免死锁
    • Linux线程同步
      • 同步概念与竞态条件
      • 条件变量
      • 条件变量函数
      • 为什么pthread_cond_wait需要互斥量
      • 条件变量使用规范
    • 总结


Linux线程安全详解

Linux线程互斥

进程线程间的互斥相关背景概念

  • 临界资源:多线程共享的资源,如全局变量。
  • 临界区:访问临界资源的代码段。
  • 互斥:确保同一时刻只有一个线程进入临界区,保护临界资源。
  • 原子性:操作不可中断,要么完成要么未完成。

进程与线程对比

  • 进程间通信需创建第三方资源(如管道、共享内存),这些资源即临界资源,访问代码为临界区。
  • 线程共享进程的大部分资源(如全局变量),无需额外创建资源即可通信。

示例:线程间通信

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int count = 0;
void* Routine(void* arg) {
    while (1) {
        count++;
        sleep(1);
    }
    pthread_exit((void*)0);
}
int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, Routine, NULL);
    while (1) {
        printf("count: %d\n", count);
        sleep(1);
    }
    pthread_join(tid, NULL);
    return 0;
}
  • count 是临界资源,被主线程和新线程共享。
  • count++printf 是临界区。

互斥与原子性问题
多线程并发操作临界资源可能导致数据不一致。例如抢票系统:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int tickets = 1000;
void* TicketGrabbing(void* arg) {
    const char* name = (char*)arg;
    while (1) {
        if (tickets > 0) {
            usleep(10000); // 模拟业务耗时
            printf("[%s] get a ticket, left: %d\n", name, --tickets);
        } else {
            break;
        }
    }
    printf("%s quit!\n", name);
    pthread_exit((void*)0);
}
int main() {
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, TicketGrabbing, "thread 1");
    pthread_create(&t2, NULL, TicketGrabbing, "thread 2");
    pthread_create(&t3, NULL, TicketGrabbing, "thread 3");
    pthread_create(&t4, NULL, TicketGrabbing, "thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    return 0;
}

运行结果可能出现负票数,原因:

  1. if (tickets > 0) 判断后线程可能切换。
  2. usleep 期间其他线程可进入临界区。
  3. --tickets 非原子操作,分为加载(load)、更新(update)、存储(store)三步,可能被打断。

互斥量mutex

互斥量(mutex)是Linux提供的锁机制,解决线程间的互斥问题,要求:

  1. 同一时刻只有一个线程进入临界区。
  2. 无线程在临界区时,允许多线程竞争进入。
  3. 线程不在临界区时不得阻止其他线程进入。

互斥量的接口

  • 初始化
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    
    • 动态分配:pthread_mutex_init(&mutex, NULL);
    • 静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 销毁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    
    • 注意:已加锁的互斥量不可销毁。
  • 加锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    • 未锁:加锁成功。
    • 已锁:阻塞等待。
  • 解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    

抢票系统改进

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int tickets = 1000;
pthread_mutex_t mutex;
void* TicketGrabbing(void* arg) {
    const char* name = (char*)arg;
    while (1) {
        pthread_mutex_lock(&mutex);
        if (tickets > 0) {
            usleep(100);
            printf("[%s] get a ticket, left: %d\n", name, --tickets);
            pthread_mutex_unlock(&mutex);
        } else {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    printf("%s quit!\n", name);
    pthread_exit((void*)0);
}
int main() {
    pthread_mutex_init(&mutex, NULL);
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, TicketGrabbing, "thread 1");
    pthread_create(&t2, NULL, TicketGrabbing, "thread 2");
    pthread_create(&t3, NULL, TicketGrabbing, "thread 3");
    pthread_create(&t4, NULL, TicketGrabbing, "thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}
  • 加锁后票数不再出现负值。
  • 注意:加锁降低并行性,需合理选择加锁范围。

互斥量实现原理探究

  • 原子性:加锁后,临界区操作对其他线程表现为原子性(要么未开始,要么已完成)。
  • 线程切换:临界区内可能切换,但锁未释放,其他线程无法进入。
  • 锁的保护:锁本身是临界资源,需保证申请过程原子性。
  • 实现机制:使用 swap/exchange 指令(单指令原子性)。
    • 伪代码
      • 加锁:xchgb 交换寄存器(清0)与 mutex(初始1),成功得1,失败得0。
      • 解锁:mutex 置1,唤醒等待线程。
    • 原理:总线周期独占,确保多核环境下原子性。

可重入VS线程安全

概念

  • 线程安全:多线程并发执行同一代码,结果一致。
  • 可重入:函数被多执行流调用,前一流未结束,后一流进入,结果无误。

常见的线程不安全的情况

  • 无锁保护的共享变量操作。
  • 函数状态随调用改变(如静态变量)。
  • 返回静态变量指针。
  • 调用线程不安全函数。

常见的线程安全的情况

  • 只读全局/静态变量。
  • 接口操作原子性。
  • 线程切换不影响结果。

常见的不可重入的情况

  • 调用 malloc/free(全局堆链表)。
  • 使用标准I/O(全局数据结构)。
  • 函数体内依赖静态数据。

常见的可重入的情况

  • 不使用全局/静态变量。
  • 不调用 malloc/new
  • 不调用不可重入函数。
  • 数据由调用者提供或局部拷贝。

可重入与线程安全联系

  • 可重入函数一定是线程安全的。
  • 不可重入函数多线程使用可能不安全。
  • 有全局变量的函数通常既不可重入也不安全。

可重入与线程安全区别

  • 可重入是线程安全的一种。
  • 加锁可使函数线程安全,但若锁未释放则不可重入。

常见锁概念

死锁

死锁是多线程互相等待对方释放资源,导致永久阻塞。

  • 单线程死锁:同一线程多次申请同一锁。

    #include <stdio.h>
    #include <pthread.h>
    pthread_mutex_t mutex;
    void* Routine(void* arg) {
        pthread_mutex_lock(&mutex);
        pthread_mutex_lock(&mutex); // 死锁
        pthread_exit((void*)0);
    }
    int main() {
        pthread_mutex_init(&mutex, NULL);
        pthread_t tid;
        pthread_create(&tid, NULL, Routine, NULL);
        pthread_join(tid, NULL);
        pthread_mutex_destroy(&mutex);
        return 0;
    }
    
    • 状态:Sl+(锁阻塞)。
  • 阻塞原理

    • 进程等待资源时,从运行队列移至资源等待队列。
    • 资源就绪后,唤醒并移回运行队列。

死锁的四个必要条件

  1. 互斥:资源独占。
  2. 请求与保持:持有资源并请求新资源。
  3. 不剥夺:资源不可强夺。
  4. 循环等待:线程间形成等待环。

避免死锁

  • 破坏任一必要条件。
  • 统一加锁顺序。
  • 避免未释放锁。
  • 一次性分配资源。
  • 使用死锁检测或银行家算法。

Linux线程同步

同步概念与竞态条件

  • 同步:在数据安全前提下,按特定顺序访问临界资源,避免饥饿。
  • 竞态条件:时序问题导致程序异常。
  • 问题:单纯加锁可能导致强竞争力线程垄断资源,其他线程饥饿。

条件变量

条件变量描述资源就绪状态,配合互斥锁实现同步:

  • 等待:线程挂起等待条件满足。
  • 唤醒:另一线程使条件满足并通知。

条件变量函数

  • 初始化
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
    
    • 静态:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 销毁
    int pthread_cond_destroy(pthread_cond_t *cond);
    
  • 等待
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    
  • 唤醒
    int pthread_cond_signal(pthread_cond_t *cond); // 唤醒首个
    int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒全部
    

示例:主线程控制子线程活动

#include <iostream>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void* Routine(void* arg) {
    pthread_detach(pthread_self());
    std::cout << (char*)arg << " run..." << std::endl;
    while (true) {
        pthread_cond_wait(&cond, &mutex);
        std::cout << (char*)arg << "活动..." << std::endl;
    }
}
int main() {
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);
    pthread_t t1, t2, t3;
    pthread_create(&t1, nullptr, Routine, (void*)"thread 1");
    pthread_create(&t2, nullptr, Routine, (void*)"thread 2");
    pthread_create(&t3, nullptr, Routine, (void*)"thread 3");
    while (true) {
        getchar();
        pthread_cond_signal(&cond); // 逐个唤醒
        // pthread_cond_broadcast(&cond); // 全部唤醒
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

为什么pthread_cond_wait需要互斥量

  • 同步需求:条件需由另一线程改变共享数据触发。
  • 死锁问题:若不释放锁,等待时持有锁导致死锁。
  • 功能
    • 等待时释放锁。
    • 被唤醒时自动加锁。
  • 错误设计
    pthread_mutex_lock(&mutex);
    while (condition_is_false) {
        pthread_mutex_unlock(&mutex); // 非原子
        pthread_cond_wait(&cond); // 可能错过信号
        pthread_mutex_lock(&mutex);
    }
    

条件变量使用规范

  • 等待
    pthread_mutex_lock(&mutex);
    while (condition_is_false)
        pthread_cond_wait(&cond, &mutex);
    // 修改条件
    pthread_mutex_unlock(&mutex);
    
  • 唤醒
    pthread_mutex_lock(&mutex);
    // 设置条件为真
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    

总结

  • 互斥:通过互斥量保护临界资源,确保数据一致性。
  • 同步:条件变量配合锁实现有序访问,避免竞态和饥饿。
  • 安全:理解可重入与线程安全,防范死锁。

相关文章:

  • 在vitepress中使用vue组建,然后引入到markdown
  • JAVASCRIPT 基础 DOM元素,MAP方法,获取输入值
  • 【AcWing】算法基础课-数学知识
  • 快速排序总结
  • Excel 豆知识 - 如何打开Online Web版 Excel/Word
  • 视频结构化框架VideoPipe-OpenCV::DNN-TensorRT安装教程【Nvidia各系列显卡-亲测有效】
  • 【数据结构】单链表
  • 【xiaozhi赎回之路-2:语音可以自己配置就是用GPT本地API】
  • 组件日志——etcd
  • 计算机操作系统(四) 操作系统的结构与系统调用
  • 【动态规划】不同路径
  • Js闭包Closure 及 其可能产生的内存泄漏问题
  • 详解 printf 打印的所有内容
  • C之(15)cppcheck使用介绍
  • 【中文翻译】第12章-The Algorithmic Foundations of Differential Privacy
  • 分布式事务与Seata
  • 题型笔记 | Apriori算法
  • 影视后期工具学习之PR(中)
  • 十亿级流量削峰实战:LinkedBlockingQueue缓冲池的工程化实现
  • 2024年MathorCup数学建模B题甲骨文智能识别中原始拓片单字自动分割与识别研究解题全过程文档加程序
  • 辽宁辽阳火灾3名伤者无生命危险
  • 人民日报:在大有可为的时代大有作为
  • 神十九飞船已撤离空间站,计划于今日中午返回东风着陆场
  • 证据公布!菲律宾6人非法登上铁线礁活动
  • 巴西外长维埃拉:国际形势日益复杂,金砖国家必须发挥核心作用
  • 西班牙葡萄牙突发全国大停电,欧洲近年来最严重停电事故何以酿成