浅谈0/1切换

 

前言:
  做过GUI开发的同学, 都知晓双缓存机制. 其过程为先把所有的场景和实体对象画到一个备份canvas, 然后再把备份canvas的内容整个填充真正的画板canvas中. 如果不采用双缓存机制, 你的画面有可能会出现闪烁和抖动.
  究其原因是整个绘制过程, 包含清屏, 绘制场景和各个实体. 其耗时远远大于单个canvas的复制. 进而导致CPU写canvas的速率小于LCD读取canvas的速率. 这样就出现闪烁的现象了.
  在后台服务中, 也会遇到类似的情形: 当数据/资源需要更新时, 采用直接增量更新的方式代价大(耗时长, 阻塞服务可用/实时响应), 由此引入back buffer,做0/1切换.
  本文以"配置文件热载更新"为例, 着重介绍0/1切换的思路和优化技巧.

热载更新:
  以往更新配置时, 往往需要重启服务进程. 为了提高服务的可用性, 更方便运维.
  采取的改进方式是:
  1) 引入配置中心服务(ConfigServer)
  把模块的配置文件搁置在ConfigServer中, 具体模块从ConfigServer中获取(拉起/通知).
  2) 监控本地配置文件变更
  进程模块通过定期轮询/事件触发的方式, 感知配置文件是否发生变化, 若发生变化, 则重新载入.
  但无论采用何种方式, 势必存在切换过程.

切换特点:
  把切换的双方定义为前端和后端, 前端资源往往被N个线程访问(静态只读), 后端资源往往是一个线程更新写. 于是就形成了一个N读1写的格局.
  具体在c/c++实现时, 切换过程往往就是一个指针的重新赋值, 十分简单.
  但问题也就隐藏在这了, 在切换后的旧资源销毁过程中, 存在多线程的竞态冲突风险.
  
  有人可能会提议, 如果对资源的访问资源的切换相同的锁保护, 就没有这个问题. 但在低频率切换的场景下, 加锁带来的性能损失, 有些得不偿失.

无锁0/1切换:
  是否存在无锁的切换方式呢?
  1). 延迟销毁
  工作线程持有并访问旧资源句柄时间不长, 可以设定一个时间窗口, 该时间窗口内属于保护期, 禁止对旧资源进行销毁.
  
  注: 在绝大多数场景下, 该方案满足条件. 只是理论上, 不排除低概率事件.
  2). 带引用计数的智能指针切换
  我们借助boost的shared_ptr来构建切换的小例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <boost/shared_ptr.hpp>
 
#include <stdint.h>
#include <stdio.h>
 
class Config {
};
 
class DataCenter {
public:
    DataCenter() {
    }
    void init() {
        active_idx = 0;
        switchover[active_idx].reset(new Config());
    }
    // *) 切换函数, 由更新线程调用
    void swith() {
        uint32_t unactive_idx = (active_idx == 0) ? 1 : 0;
        uint32_t old_active_idx = active_idx;
     
        // *) 新资源ready   
        switchover[unactive_idx].reset(new Config());
        // *) 正式切换
        active_idx = unactive_idx;
         
        // *) 旧资源reset, 引入计数减一
        switchover[old_active_idx].reset();
    }
 
    // *) 访问资源, 由前端线程调用
    boost::shared_ptr<Config> getConfig() {
        return switchover[active_idx];
    }
 
private:
    volatile uint32_t active_idx;
    // *) 切换数组
    boost::shared_ptr<Config> switchover[2];
};

  巧用boost::shared_ptr内部有个原子计数器代理指针, 借助RAII的思想完美的实现了无引用时的自动清理工作. 也避免了上述的竞态冲突.

总结:
  在服务模块中的0/1切换有很多, 这边简述了下解决方案, 没有细致展开, 权当个人的学习笔记.

写在最后:
  
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.

   

 

posted on   mumuxinfei  阅读(896)  评论(2编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2014-04-30 书评<<剑指offer 名企面试官精讲典型编程题>>
2014-04-30 Hive cli源码阅读和梳理

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示