type
date
slug
category
icon
password
不要问"如何传递全局变量",要问"为何需要全局变量"。
1. 根本原因:全局变量为何产生
全局变量不是技术问题,而是设计问题。理解其根源,才能从根本上解决。
1.1 状态共享的本质需求
程序的本质是数据处理。组件间不可避免地需要共享数据:
- 跨函数共享:多个函数操作同一份配置
- 跨模块共享:不同模块需要访问公共资源
- 跨线程共享:并发执行的代码需要协调状态
这是正当需求,问题在于如何满足这一需求。
1.2 全局变量是"懒惰"的解决方案
面对状态共享需求,全局变量是最小阻力路径:
为何"懒惰"?
特征 | 说明 |
隐式依赖 | 函数不声明需要什么,直接访问 |
零成本访问 | 无需参数传递,代码更短 |
缺乏约束 | 语言层面不强制显式化 |
这不是批评——在小型脚本中,全局变量完全合理。问题出在规模扩大后。
1.3 问题演化路径
全局变量的危害是渐进式的:
关键洞察:全局变量的问题不在于"全局",而在于隐式依赖和不可控的状态变化。
2. 基础接口设计:从函数签名开始
解决全局变量问题的第一步,从最小单元——函数签名——开始。
2.1 显式化原则:参数即契约
函数签名是契约,声明"我需要什么,我产出什么"。
❌ 隐式依赖(全局变量)
✅ 显式依赖(参数传递)
显式化的收益:
收益 | 说明 |
可读性 | 看签名就知道依赖 |
可测试性 | 传入不同参数即可测试 |
可重用性 | 不绑定特定全局变量 |
2.2 接口设计的三个层次
从简单到复杂,有三种显式化程度递增的方法:
层次 | 方法 | 效果 | 适用场景 |
L1 数据传递 | 参数 / 返回值 | 避免共享状态 | 简单函数 |
L2 依赖注入 | 传递对象 / 接口 | 依赖关系显式 | 组件协作 |
L3 纯函数 | 无副作用 | 根本消除全局状态 | 核心逻辑 |
依赖注入的详细实现见第3节。
2.3 函数式思维:状态外置化
纯函数是消除全局状态的终极武器:
- 定义:相同输入 → 相同输出,无副作用
- 思维转变:状态不"存在于"函数中,而是"流经"函数
核心原则:状态作为参数传入,新状态作为返回值传出。
3. 依赖注入:战术层面的核心工具
回顾第2节,依赖注入(DI)是 L2 层次的显式化手段。本节深入实现细节。
3.1 DI 解决的核心问题
问题 | DI 如何解决 |
隐式依赖 | 依赖通过构造函数/参数显式声明 |
难以替换 | 注入接口而非实现,可随时替换 |
测试困难 | 注入 Mock 对象,隔离测试 |
生命周期混乱 | 集中管理对象创建和销毁 |
3.2 DI 的三种实现方式
方式1:构造函数注入(推荐)
方式2:上下文对象
方式3:DI 容器/框架
3.3 DI 容器的选择标准
场景 | 推荐方式 | 理由 |
轻量级/脚本 | 手动注入 | 简单直接,无额外依赖 |
中型项目 | 上下文对象 | 结构清晰,易于传递 |
大型系统 | DI 框架 | 自动装配,生命周期管理 |
3.4 DI 的边界
DI 不是银弹,有其适用边界:
场景 | 是否适合 DI | 原因 |
业务逻辑层 | ✅ 适合 | 需要可测试、可替换 |
可替换组件 | ✅ 适合 | 核心价值所在 |
性能关键路径 | ⚠️ 谨慎 | 间接调用有开销 |
硬件寄存器访问 | ❌ 不适合 | 必须直接访问 |
跨语言边界 | ❌ 不适合 | C 回调无法携带上下文 |
4. 模块设计:封装与边界
模块是代码组织的基本单元。正确的模块设计能将状态控制在最小范围。
4.1 模块的两种性质
类型 | 特征 | 示例 |
无状态模块 | 只包含纯函数 | 数学计算、字符串处理 |
有状态模块 | 封装内部状态 | 数据库连接池、配置管理 |
原则:优先创建无状态模块;有状态模块需要显式管理。
4.2 有状态模块的三种封装模式
模式 | 适用场景 | 状态可见性 | 代码示例 |
类实例 | 通用场景 | 私有 | obj = MyClass() |
单例 | 资源池/配置 | 受控访问 | Config.instance() |
模块级变量 | 硬件约束 | 包私有 | _internal_state |
模式A:类实例(首选)
模式B:单例 + 显式初始化
模式C:受控的模块级变量(特殊场景)
4.3 模块级变量的使用准则
✅ 允许情况:
- 硬件寄存器映射(无法避免)
- 性能关键路径(C 语言回调)
- 中断服务程序(ISR)
⚠️ 使用约束:
- 必须提供显式
init()函数
- 必须提供测试重置钩子
_reset_for_testing()
- 必须用锁保证线程安全
- 必须文档化并发安全性
4.4 消除模块级变量的重构手法
将模块级变量重构为类实例:
5. 架构设计:控制依赖方向
单个模块的设计只是局部优化。系统级别需要架构设计来控制整体状态流向。
5.1 分层架构的核心思想
- 分层:不同层级有不同的状态管理策略
- 依赖方向:外层依赖内层,内层不知道外层存在
- 稳定性梯度:内层最稳定(纯逻辑),外层可变(IO/硬件)
5.2 典型分层策略
5.3 各层的状态管理原则
层级 | 状态策略 | 全局变量 | 原因 |
领域层 | 纯函数 | ❌ 禁止 | 核心逻辑需可测试 |
应用层 | 依赖注入 | ❌ 禁止 | 需要可替换 |
基础设施层 | 模块封装 | ⚠️ 受控允许 | 硬件/性能约束 |
5.4 依赖倒置原则(DIP)
传统依赖:高层 → 低层
倒置依赖:高层 → 抽象 ← 低层
结果:配置/资源/状态由外层注入,内层不持有。
6. 状态管理范式选择
不同场景需要不同的状态管理范式。本节提供选择框架。
6.1 范式选择决策矩阵
场景特征 | 推荐范式 | 原因 |
单线程应用 | 共享状态 + DI | 性能最优,简单直接 |
多线程 + 低频写 | 不可变对象 | 无锁共享,避免竞态 |
多线程 + 高频写 | Actor / CSP | 状态隔离,消息通信 |
分布式系统 | 事件驱动 | 解耦,异步处理 |
6.2 两种根本范式
范式1:共享状态(OOP 主流)
- ✅ 优点:直观、性能高
- ❌ 缺点:隐式依赖、并发问题
范式2:消息传递(函数式/Actor)
- ✅ 优点:解耦、并发安全
- ❌ 缺点:性能开销、心智负担
6.3 混合策略最佳实践
实际项目通常采用混合策略:
状态类型 | 管理方式 | 示例 |
内部状态 | 组件私有 | 类成员变量 |
跨组件通信 | 消息/事件 | 事件总线 |
配置/资源 | 依赖注入 | Context 对象 |
6.4 从共享状态到消息传递的迁移路径
当共享状态出现问题时,可按以下步骤迁移:
迁移示例:
7. 实践决策树
理论需要落地。本节提供实践决策工具。
7.1 场景分类决策
7.2 改造策略矩阵
现状 | 短期方案 | 长期方案 |
全局配置对象 | 封装为 Config 类,注入使用处 | 不可变配置 + 环境变量 |
全局状态变量 | Context 对象传递 | 状态机 + 事件驱动 |
全局缓存/连接池 | 单例 + 显式初始化 | 资源管理器 + DI |
ISR 全局标志位 | 模块封装 + 队列桥接 | 无法避免,接受 |
7.3 常见反模式警示
反模式1:过度 DI
反模式2:伪装的全局变量
反模式3:状态爆炸
8. 工程实践指南
8.1 端到端重构案例
Before:全局变量密集的代码
After:依赖注入重构
重构收益:
指标 | Before | After |
依赖可见性 | 隐式 | 显式(签名声明) |
单元测试 | 需要 mock 全局变量 | 注入 mock 对象 |
并行测试 | 不可行 | 完全支持 |
可重用性 | 绑定全局变量 | 任意组合 |
8.2 代码审查检查点
函数签名是否声明所有依赖?
全局变量是否有显式初始化入口?
测试是否需要重置全局状态?
并发场景下是否有竞态条件?
模块级变量是否有线程安全保护?
8.3 重构优先级
优先级 | 目标 | 方法 | 案例对应 |
P0 | 核心业务逻辑 | 立即消除全局变量(DI) | OrderService 重构 |
P1 | 配置管理 | 封装为不可变对象 | config → AppContext |
P2 | 基础设施层 | 模块级封装 + 线程安全 | db_connection → 注入 |
P3 | 性能关键路径 | 评估后决定 | 缓存保留简单实现 |
完整测试示例:
8.4 测试友好性指标
指标 | 目标状态 | 检验方法 |
无需全局状态 setup/teardown | ✅ | 测试可独立运行 |
可并行运行测试 | ✅ | pytest -n auto 无冲突 |
Mock 对象容易注入 | ✅ | 只需实现 Protocol |
9. 结论:根本解决之道
9.1 三个层次的解决方案
层次 | 方法 | 效果 |
基础接口 | 显式参数 + 纯函数 | 依赖关系透明 |
模块设计 | 封装 + 边界控制 | 状态局部化 |
架构设计 | 分层 + 依赖倒置 | 依赖方向可控 |
9.2 核心原则
- 显式化:依赖通过接口声明,不隐式访问
- 局部化:状态尽可能限制在最小作用域
- 流动化:数据作为参数/返回值流动,不驻留
- 分层化:不同层级不同策略,不是一刀切
9.3 最终答案
全局变量不是技术问题,是设计问题:
- 根源在于隐式依赖和状态共享范式
- 解决方案是显式化依赖和控制状态流动
- 工具是参数传递、依赖注入、事件驱动
- 目标是可测试、可维护、可扩展
技术选择的本质是权衡。不存在"绝对不能用"的技术,只有"不适合场景"的应用。
全局变量在小型脚本中完全合理;在大型系统中需要被替代。
关键不是消灭全局变量,而是让依赖关系显式化、可追踪、可控制。
Linux 内核"不使用全局变量"的设计实践
按层次归纳的解决方案
层次 | 设计模式 | 内核实例 | 核心机制 | 解决的问题 |
L1 基础接口
(显式依赖) | 描述符传递 | gpio_desc, clk, regulator, reset_control, phy, pwm_device | 不透明指针替代全局 ID | 类型安全、逻辑语义封装 |
上下文参数 | 所有系统调用的 struct file *filp | 每次调用传递上下文 | 避免进程全局状态 | ㅤ |
私有数据指针 | dev_get_drvdata() / platform_set_drvdata() | 设备与驱动数据关联 | 多实例驱动无全局变量 | ㅤ |
回调携带 context | 中断处理 irqreturn_t (*handler)(int, void *dev_id) | dev_id 参数传递私有数据 | 共享中断线场景 | ㅤ |
L2 模块设计
(状态封装) | per-device 结构体 | struct net_device, struct block_device | 设备实例自带状态 | 支持多设备同时存在 |
per-CPU 变量 | DEFINE_PER_CPU(), this_cpu_ptr() | 每 CPU 独立副本 | 消除缓存竞争 | ㅤ |
命名空间 | PID namespace, net namespace | 进程/网络栈隔离 | 容器化资源隔离 | ㅤ |
RCU 保护的全局数据 | 路由表、文件系统挂载点 | 读多写少场景的无锁共享 | 高性能并发读 | ㅤ |
kobject 树 | /sys 目录层次 | 对象生命周期管理 | 自动引用计数 | ㅤ |
L3 架构设计
(依赖控制) | 设备模型 | device - driver - bus 三层 | 依赖注入 + 工厂模式 | 驱动与设备解耦 |
VFS 抽象层 | struct file_operations | 接口多态 | 文件系统实现可插拔 | ㅤ |
网络协议栈 | struct net_proto_family | 协议注册机制 | 协议模块动态加载 | ㅤ |
subsystem 架构 | input、regulator、DMA engine | 生产者-消费者分离 | 跨平台代码复用 | ㅤ |
关键设计模式详解
1. per-CPU 变量(L2 层)
原理:
- 每个 CPU 持有独立副本
- 无缓存行竞争
- 访问无需加锁
典型应用:
- 统计计数器(
struct kernel_stat)
- 中断上下文数据(
struct irq_desc的 per-CPU 部分)
- 网络收发队列
2. 设备私有数据(L2 层)
关键点:
- 驱动可支持多个设备实例
- 状态完全封装在
struct my_device
- 通过
platform_device作为句柄查找
3. 命名空间(L3 层)
替代方案对比:
场景 | 传统做法(全局变量) | 命名空间做法 |
网络栈 | 全局路由表 ip_rt_table | 每 namespace 独立 net->ipv4.fib_table |
进程树 | 全局 init_task | 每 PID namespace 的 init |
文件系统 | 全局挂载点 | 每 mount namespace 独立视图 |
4. 设备模型的依赖注入(L3 层)
依赖流动:
硬件约束场景的处理
必须使用全局变量的场景(L2 封装):
场景 | 原因 | 内核解决方案 |
中断服务程序 (ISR) | C 回调无法携带上下文 | 通过 request_irq() 的 dev_id 参数传递 |
硬件寄存器映射 | 固定物理地址 | ioremap() 后存储在设备私有结构体 |
早期初始化代码 | 设备模型尚未就绪 | early_param() + 转换为 per-device 配置 |
per-CPU 中断栈 | 性能关键路径 | 用 per-CPU 变量代替全局变量 |
与你页面原则的对应关系
页面原则 | 内核体现 |
显式化 | 描述符 API、上下文参数、回调 dev_id |
局部化 | per-device 结构体、per-CPU 变量、命名空间 |
流动化 | VFS 层的 file 指针、网络栈的 skb 传递 |
分层化 | 设备模型、subsystem 架构、协议栈分层 |
核心洞察:内核是 C 语言对"不使用全局变量"的工程级实践,通过结构体指针模拟面向对象的依赖注入。
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/remove_global_variables
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!









