【Linux系统:信号】线程安全不等于可重入:深度拆解变量作用域与原子操作

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • 前言
  • [1~> 逻辑拆解](#1~> 逻辑拆解)
    • [(1) 局部变量的本质](#(1) 局部变量的本质)
    • [(2) 原子操作的作用](#(2) 原子操作的作用)
    • [(3) 不可重入的判定标准](#(3) 不可重入的判定标准)
    • [(4) 核心结论](#(4) 核心结论)
  • [2 ~> 从两个维度,深入理解可重入性(Reentrancy)](#2 ~> 从两个维度,深入理解可重入性(Reentrancy))
  • 结尾

前言

  • 在一个函数中若对局部变量进行了原子操作,则这个函数一定是不可重入的

这个表述正确吗?

这个观点是不正确的。"使用了原子操作""函数是否可重入"之间没有必然的因果关系 。 事实上,在一个函数中对局部变量进行原子操作,通常并不会导致函数变得不可重入。


1~> 逻辑拆解

(1) 局部变量的本质

局部变量(非 static 修饰)存储在每个线程独立的 **栈(Stack)**空间中。

  • 当多个线程同时调用同一个函数时,每个线程都有自己独立的局部变量副本。

  • 既然每个线程操作的是完全不同的内存地址,它们之间不存在资源竞争,自然也就不会破坏重入性。

(2) 原子操作的作用

原子操作(Atomic Operations)的主要目的是解决多个执行流(线程或中断处理程序)访问共享资源(如全局变量、静态变量、堆内存)时的竞态条件。

  • 如果在函数内对全局变量进行原子操作,该函数依然可能是不可重入的(虽然它是线程安全的)。

  • 但如果在函数内仅对局部变量进行原子操作,这通常是多此一举,因为栈上数据本身就是线程私有的,不需要原子性保护。

(3) 不可重入的判定标准

一个函数之所以不可重入,通常是因为它满足了以下条件之一:

使用了静态(static)数据结构或全局变量。

调用了 mallocfree(它们通常维护全局的堆链表)。

调用了标准 I/O 函数(其内部使用了全局数据结构)。

返回了指向静态数据的指针。

(4) 核心结论

判定重入性的核心在于对共享状态的依赖

  • 可重入函数:即使被中断并在中断处理程序中再次调用,返回后原调用逻辑依然正确。

  • 示例:

cpp 复制代码
void func(int x) {
    std::atomic<int> local_val = x; // 局部变量
    local_val++; // 对局部变量的原子操作
    // 这个函数依然是可重入的,因为所有操作都在栈上
}

总结 :只要函数不操作全局/静态变量,不调用其他不可重入函数,即使内部有原子操作,它依然是可重入的。原子操作通常是为了实现"线程安全",而"可重入"是比"线程安全"更严格的要求。


2 ~> 从两个维度,深入理解可重入性(Reentrancy)

接下来,艾莉丝想细说一下可重入函数和不可重入函数,艾莉丝打算结合包括什么局部变量、全局变量、静态变量等变量类型、原子操作等知识点,帮助理解什么是可重入函数、不可重入函数。

要深入理解可重入性(Reentrancy) ,必须区分两个维度:代码逻辑的隔离性 (是否使用共享资源)和执行流的原子性(操作是否会被打断导致状态破坏)。

我们可以通过内存布局和资源访问权限来拆解。

(1) 变量类型与重入性的关系

函数是否可重入,核心取决于它如何处理数据。

**局部变量(栈变量)**加粗样式

每个线程/进程在调用函数时,都会在自己的私有栈中分配局部变量。不同执行流之间的数据互不干扰。

  • 结论 : 只使用局部变量的函数通常是可重入的。

全局变量与静态变量(数据段)

它们存储在全局数据段,被所有执行流共享。如果函数修改了这些变量,当它被中断并再次进入(重入)时,第二次执行会覆盖第一次执行的中间结果,导致第一次执行恢复时数据错乱。

  • 结论 : 修改全局或静态变量的函数通常是不可重入的。

堆变量(malloc/new)

虽然堆空间由程序员分配,但其底层管理(空闲链表)是全局共享的。

  • 结论 : 调用 mallocfree 的函数通常是不可重入的,因为堆管理器本身维护着全局状态。

(2) 原子操作在其中的角色

原子操作(Atomic Operation)是指不可被中断的操作。它在并发场景下有两个层面的意义:

1、解决竞态条件 : 在多个线程访问同一个全局变量时,原子操作能保证数据的"原子性"(即线程安全),但它不等于可重入。

2、原子性不代表逻辑一致性

  • 假设函数 update() 使用原子操作修改全局变量 g_count

  • 线程 A 执行到一半,g_count 被原子地修改了。

  • 此时中断发生,中断处理函数也调用了 update(),再次修改了 g_count

  • 线程 A 恢复后,虽然 g_count 的数值是正确的(没有出现数据撕裂),但如果线程 A 依赖于 g_count 在中断前后的一致性,其逻辑依然会崩溃。

结论: 原子操作主要用于解决"线程安全"问题,而可重入性要求更高------它要求函数即便在任何点被"切走"再"切回",逻辑必须完好如初。

(3) 核心对比表

(4) 为什么"加锁"不能解决重入问题?

这是一个常见的误区。为了线程安全,我们常给全局变量加锁(Mutex)。

  • 如果一个函数在持有锁的情况下被中断。

  • 中断处理函数(同一个执行流)尝试再次获取该锁。

  • 由于锁已经被自己(在被中断的状态下)持有,函数会陷入死锁。

结论: 加锁可以实现线程安全,但往往会破坏重入性。可重入函数的设计原则是**"不共享"**而非"保护共享"。

(5) 总结:编写可重入函数的准则

  • (1) 不使用全局变量或静态变量。
  • (2) 不返回指向静态数据的指针。
  • (3) 所有数据由调用者通过参数提供(使用调用者的栈空间)。
  • (4) 不调用任何不可重入的库函数。

你之前提到的"对局部变量进行原子操作"之所以不影响重入性,是因为局部变量本身就在隔离的栈中,即便没有原子保护,它们也是天然隔离的。


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!" "技术之路难免有困惑,但同行的人会让前进更有方向。" |

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【Linux系统:多线程】Linux 内核与多线程深度强化干货25条

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
笑鸿的学习笔记2 小时前
Qt与CMake笔记之option、宏传递与Qt Creator项目设置
开发语言·笔记·qt
楼田莉子2 小时前
同步/异步日志系统:日志的工程意义及其实现思想
linux·服务器·开发语言·数据结构·c++
无心水2 小时前
20、Spring陷阱:Feign AOP切面为何失效?配置优先级如何“劫持”你的设置?
java·开发语言·后端·python·spring·java.time·java时间处理
QfC92C02p2 小时前
C# 中的 Span 和内存:.NET 中的高性能内存处理
java·c#·.net
kpl_202 小时前
特殊类设计、类型转换和IO流(C++)
c++
牢姐与蒯2 小时前
栈和队列的实现
c++
red_redemption2 小时前
自由学习记录(155)
学习
cccyi72 小时前
【C++ 脚手架】brpc 的介绍与使用
c++·rpc·brpc
0xDevNull2 小时前
Java 21 新特性概览与实战教程
java·开发语言·后端