zoukankan      html  css  js  c++  java
  • 浅谈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来构建切换的小例子

    #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切换有很多, 这边简述了下解决方案, 没有细致展开, 权当个人的学习笔记.

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

       

  • 相关阅读:
    数据链路层
    补码加减法
    matlab函数
    HDU2159_二维完全背包问题
    HDU2844买表——多重背包初探
    HDU1025贫富平衡
    最大m段子段和
    01背包浮点数情况
    第K大01背包
    HDU2955 01背包
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/4466000.html
Copyright © 2011-2022 走看看