双缓冲区方案
- 双缓冲方案分配两段缓冲区:读(当前)缓冲和写(下一)缓冲。当执行查询操作时,总是读取当前缓冲区;当执行写操作时,总是在下一缓冲区上操作。当写操作执行完成后,交换操作会立刻将当前缓冲区和下一缓冲区交换,这样,最近更新的缓冲区便可供查询使用,旧的查询缓冲区用于写操作。
- 示例代码:
template <typename T>
class DoubleBuffer {
public:
DoubleBuffer():flag(false){}
T* write() {
return flag ? &buffer1 : &buffer2;
}
T* read() {
return flag ? &buffer2 : &buffer1;
}
void swap() {
flag = !flag;
}
private:
bool flag;
T buffer1;
T buffer2;
};
template <typename T>
class Updater {
public:
T* reload_handler() {
model.write()->clear();
return model.write();
}
T* inc_handler() {
*model.write() = *model.read();
return model.write();
}
int done() {
model.swap();
return 0;
}
T* read() {
return model.read();
}
private:
DoubleBuffer<T> model;
};
使用
#include <iostream>
#include <map>
#include "Updater.hpp"
int main(){
Updater<std::map<int, int> > update_map;
std::map<int, int> *p = update_map.reload_handler();
p->insert(std::make_pair(1, 100));
p->insert(std::make_pair(2, 200));
update_map.reload_handler()->insert();
update_map.done();
// update_map中存有:1 100 2 200
std::map<int, int> *pr = update_map.read();
p = update_map.inc_handler();
p->insert(std::make_pair(10, 100));
p->insert(std::make_pair(20, 200));
update_map.done();
// update_map中存有:1 100 2 200 10 100 20 200
pr = update_map.read();
}
安全性的考虑
1.在当前五分钟定时加载,每次加载时长两分钟左右,上述代码完全够用
2.代码依旧存在可能出现问题的情况,但是该可能微乎其微。
——当每次加载时长几乎和定时加载间隔相同时有可能出现如下问题——对正在读的缓冲执行写操作
1. 线程1中,更新操作w更新write_buffer
2. 线程2中,查询操作r指向read_buffer
3. 线程1中,更新操作w完成后执行交换
4. 线程2中ii步骤r操作持续,同时,线程1中新的更新操作开始。此时线程1和线程2会操作同一块内存
考虑到查询操作速度远高于更新操作,出现问题的的概率极低
改进方案
方案1: 计数
可以通过使用shared_ptr指针,看当前内存块引用数量是否仅为1,为1时再允许写操作,否则等待。
方案2: 三buffer方案
该方案增加一个缓冲区,分别称为只读buffer、只写buffer、读写隔离buffer
切换函数示例如下
void switch_buffer()
{
*_cache_buffer = *_write_buffer;
T* tmp = _write_buffer;
_write_buffer = _cache_buffer;
_cache_buffer = _read_buffer;
_read_buffer = tmp;
}
以R、W、C分别表示只读buffer、只写buffer、读写隔离buffer;三块内存原始数据;r、w、c分别表示指向三块内存的指针;tmp为辅助指针;A、B、C三列为三个内存块,则上述流程可表示如下:
可以看出与双buffer相比,三buffer交换完成后将读指针切换到了最近更新的(写)内存,而旧的读内存块将持续存在一段时间,这一定程度保证了对旧内存块执行读操作的安全性。写操作则在第三块内存上执行。