【一文搞懂可重入与线程安全的关系】

一、什么是可重入函数?

  在多线程或中断环境中能够被安全调用的函数。
  1、不依赖于全局或静态变量:
    如果函数使用了全局或静态变量,多个线程或中断可能同时修改它,导致数据竞争。
    可重入函数不会使用这些变量,或者在使用时采取保护措施(例如传递局部变量或锁定访问)。
  2、不依赖于非线程安全函数:
    如果一个函数调用了非线程安全函数,那么它本身也不是可重入的。
  3、使用局部变量:
    所有需要的数据都存储在局部变量或通过参数传递,以确保线程间互不干扰。
  4、避免静态分配的内存:
    避免在函数内部使用像 malloc 或 free 这样的静态内存分配方法。

二、两种实现可重入的方式

1. 只使用局部变量

int sum(int a, int b) {
    int result = a + b;  // 局部变量,线程间互不干扰
    return result;
}

下面从汇编层面解释下为什么局部变量是可重入的:
  int sum(int a, int b) 函数中的局部变量 result 之所以能在多线程中互不干扰,是因为局部变量 result 的内存空间是在栈帧中分配的,每个线程都有自己独立的栈空间!因此 result 的值在不同线程间不会共享或冲突。汇编如下:(x86-64 gcc 14.2)
在这里插入图片描述
  上面RBP和RSP是堆栈基地址,EDI和ESI、EDX是局部寄存器,均不存在多线程竞争,唯一需要注意的是EAX,中间可能被别人修改。
在这里插入图片描述
  函数栈的工作原理【截图自《汇编语言程序设计(美) 布鲁姆》,图虽包浆,但是经典】
在这里插入图片描述
如上所示,寄存器EBP是堆栈基址指针,函数参数从右至左依次入栈后,EBP会上移(往高地址方向),ESP则指向原来的EBP。

2. 线程本地存储(Thread-Local Storage, TLS)

thread_local int thread_local_var = 0;

void increment() {
    thread_local_var++;
}

  每个线程都有自己的 TLS 区域,变量 thread_local_var 在不同线程中是完全独立的,不共享存储空间。由于不同线程的 fs 段寄存器指向不同的 TLS 基址,读取和写入操作只会影响当前线程的变量。
常见的可重入函数
  数学函数:如 sin, cos, sqrt,斐波那契函数 等(只读,无全局变量依赖)
  内存管理函数:如 memcpy, strlen 等(不修改全局变量)

三、可重入与线程安全是一样的么?

!不一样 !

1.可重入函数

  可重入函数是指在任意时刻,当一个函数被多个线程或同一个线程递归调用时,总能正确运行而不产生冲突。
  特点:
    1、无共享状态:不使用任何非本地的静态或全局变量。
    2、无副作用:函数的行为仅依赖传入的参数,不会修改外部环境。
    3、局部变量独立:使用的局部变量都保存在函数调用栈上。
    4、不依赖线程同步机制。

2.线程安全

  线程安全是指在多线程环境中,当多个线程同时调用一个函数或访问某个数据时,不会出现数据竞争或导致程序行为异常。
  特点:
    1、线程安全的代码可能依赖某些同步机制(如锁、信号量)。
    2、可能涉及全局变量或共享资源,但必须通过线程同步机制保证数据一致性。
    3、不要求嵌套调用或递归调用一定安全
在这里插入图片描述

3.线程安全的函数一定是可重入的吗?

不一定,线程安全的函数可能依赖锁,导致同一线程递归调用时死锁,失去可重入性。
例:

std::mutex mtx;

void safe_increment(int& counter) {
    std::lock_guard<std::mutex> lock(mtx);  // 锁定共享资源
    counter++;
}

  当一个线程调用 mtx.lock() 后,如果同一个线程再次调用 mtx.lock(),会进入死锁状态。

4.可重入的函数一定是线程安全的吗?

一定是,可重入函数没有共享状态,自然不存在数据竞争问题,因此是线程安全的。
  可重入函数适用于嵌套调用、底层算法实现(如数学函数 sin、log 等)、不涉及共享资源的独立逻辑。
  线程安全函数适用于:多线程并发场景、涉及全局变量或共享资源的访问。

四、可重入锁(递归锁)std::recursive_mutex

  上面说到当一个线程调用 mtx.lock() 后,如果同一个线程再次调用 mtx.lock(),会进入死锁状态。
那么解决办法是什么呢?
  那就是std::recursive_mutex,其lock() 次数和 unlock() 次数要保证相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。可以多个线程同时进入,虽然访问了外部变量,但也算是可重入吧。
  不过递归锁的使用会带来性能开销,且可能掩盖设计问题,要是能不用就尽量不用吧,出问题了不好定位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值