目录
- 1、双缓冲区 读写分离
- 2、后台线程定时更新数据
- 3、类设计
- 完整代码
- cache.cpp
- cache.h
- main.cpp
- makefile
读写分离的双缓冲buffer有以下好处:
- 提高了并发读写的效率:在多线程环境下,读写操作是相互竞争的,读写分离的双缓冲buffer可以有效地减少读写之间的竞争,提高并发读写的效率。
- 减少了数据的拷贝:双缓冲buffer可以将数据从写缓冲区直接拷贝到读缓冲区,避免了中间数据拷贝的过程,提高了数据读写的效率。
- 提高了数据的安全性:双缓冲buffer可以避免读写操作的交叉,从而保证了数据的安全性。
- 提高了系统的稳定性:双缓冲buffer可以避免读写操作的阻塞,从而提高了系统的稳定性和可靠性。
1、双缓冲区 读写分离
单缓冲存在读写冲突的问题,即同时进行读写操作时容易出现数据不一致的情况。而采用双缓冲则可以让读写操作互不干扰。
具体表现
读取操作只会访问当前可读的缓存,写入操作只会访问当前可写的缓存。
每次更新数据时,先修改写缓存值并将其序列化到共享内存中,然后交换读写部分索引,使得读部分开始访问最新可用的数据。
流程如下:
互斥阶段
写线程的交换索引这一步需要加锁,锁粒度很小
读线程读取的时候也需要加锁防止写线程修改索引,锁粒度也仍然很小
2、后台线程定时更新数据
提供了一个定期更新数据的线程函数,通过调用SDK获取新数据,
并通过 judge_valid 判断新数据是否有效,如果有效,则调用 update_value 更新到可写缓存中,并更新共享内存。
最后,在析构函数中等待更新线程结束。
3、类设计
// 数据缓存类 不可继承
class DataCache final
{
public:
// 获取单例实例
static DataCache& instance(key_t key) {
static DataCache inst(key);
return inst;
}
// 禁止拷贝与赋值
DataCache(const DataCache&) = delete;
DataCache& operator=(const DataCache&) = delete;
PolarisInstance get();
inline key_t getKey() { return m_shm_key; }
private:
DataCache(key_t key);
~DataCache();
PolarisInstance fetchValueFromExternalSource() const;
bool serializePolarisInstance(const PolarisInstance& obj);
bool deserializePolarisInstance(PolarisInstance& obj);
void updateValue(const PolarisInstance& value);
bool judgeValid(const PolarisInstance& value);
void updateThreadFunc(int intervalMs);
using CacheArray = std::array<PolarisInstance, 2>; // 缓存数组类型定义
CacheArray m_cache; // 双缓冲缓存数组
size_t m_readIndex; // 可读部分索引
size_t m_writeIndex; // 可写部分索引
std::thread m_updateThread; // 更新数据的线程
bool m_thread_stopFlag; // update_thread的停止标志
std::mutex m_mtx; // lock
static const size_t SHM_SIZE = sizeof(PolarisInstance);
key_t m_shm_key;
};
完整代码
cache.cpp
//
// Created by hanhandi on 2023/3/14.
//
#include <iostream>
#include "Cache.h"
#include <sys/ipc.h> // ftok
namespace Cache {
// 获取当前时间戳(ms)
static long long current_timestamp() {
auto timestamp_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now());
return timestamp_ms.time_since_epoch().count();
}
// 生成随机 IPv4 地址
static char* generate_random_ipv4(unsigned int seed) {
// 使用时间戳作为种子初始化随机数生成器
std::mt19937 generator(seed);
// 分为四个段,每个段取值范围为 0~255
std::uniform_int_distribution<int> distribution(0, 255);
int a = distribution(generator);
int b = distribution(generator);
int c = distribution(generator);
int d = distribution(generator);
// 将生成的随机值拼接成 IPv4 地址
std::ostringstream oss;
oss << a << '.' << b << '.' << c << '.' << d;
// 将 std::string 转换为 char*
static char ipv4_address[16];
strncpy(ipv4_address, oss.str().c_str(), sizeof(ipv4_address));
ipv4_address[sizeof(ipv4_address) - 1] = '\0';
return ipv4_address;
}
Instance::Instance() {
long long timestamp = current_timestamp();
auto res = generate_random_ipv4((unsigned int)timestamp);
strncpy(m_ip, res, sizeof(m_ip));
m_ip[sizeof(m_ip) - 1] = '\0';
}
Instance::Instance(const std::string& str) {
strncpy(m_ip, str.c_str(), sizeof(m_ip));
}
bool Instance::Isvalid() const {
// TODO
return true;
}
Instance DataCache::get() {
std::lock_guard<std::mutex> guard(m_mtx);
return m_cache[m_readIndex];
}
DataCache::DataCache(key_t key) : m_readIndex(0), m_writeIndex(1), m_shm_key(key) {
// 获取共享内存ID和指针
int shmId = shmget(m_shm_key, sizeof(Instance), 0666);
std::cout << shmId << std::endl;
if (shmId == -1) {
auto val = Instance();
m_cache[m_readIndex] = val;
} else {
// auto val = loadFromShm<Instance>();
auto val = Instance();
auto res = deserializeInstance(val);
m_cache[m_readIndex] = val;
}
// 创建并启动新线程
m_updateThread = std::thread(&DataCache::updateThreadFunc, this, 500);
m_thread_stopFlag = false;
printf("init\n");
}
DataCache::~DataCache() {
// 等待更新线程结束
m_thread_stopFlag = true;
if(m_updateThread.joinable()) {
m_updateThread.join();
}
printf("destroy\n");
}
// 从外部获取新数据的函数,需要用户实现
Instance DataCache::fetchValueFromExternalSource() const {
// TODO: 实现从外部获取新数据的逻辑
return Instance(); // 这里返回默认构造的数据
}
bool DataCache::serializeInstance(const Instance& obj) {
int shmid = shmget(m_shm_key, sizeof(obj), IPC_CREAT | 0666);
std::cout << "shmid" << shmid << std::endl;
void* shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*) -1) {
std::cout << "链接失败" << std::endl;
return false; // 连接共享内存失败
}
// 将对象的成员变量拷贝到共享内存中
memcpy(shmaddr, &obj, sizeof(Instance));
// 解除内存连接
shmdt(shmaddr);
std::cout << "链接成功" << std::endl;
return true;
}
// 从共享内存段中读取字节序列并反序列化为Instance类对象
bool DataCache::deserializeInstance(Instance& obj) {
int shmid = shmget(m_shm_key, sizeof(obj), 0666);
std::cout << "shmid" << shmid << std::endl;
void* shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*) -1) {
std::cout << "链接失败" << std::endl;
return false; // 连接共享内存失败
}
// 从共享内存中读取字节序列,并将其转换为对象的成员变量
memcpy(&obj, shmaddr, sizeof(Instance));
// 解除内存连接
shmdt(shmaddr);
std::cout << "链接成功" << std::endl;
return true;
}
// 更新数据
void DataCache::updateValue(const Instance& value) {
// 获取当前可写缓存
auto& cache = m_cache[m_writeIndex];
// 修改缓存值
cache = value;
// 修改共享内存
// int update_shm_res = saveToShm<Instance>(value);
bool update_shm_res = serializeInstance(value);
std::cout << "update_shm_res:" << update_shm_res << std::endl;
// 尝试交换读写部分索引
std::lock_guard<std::mutex> guard(m_mtx);
std::swap(m_writeIndex, m_readIndex);
// std::cout << "m_writeIndex:"<< m_writeIndex << ", m_readIndex:"<< m_readIndex << "\n"<< std::endl;
}
bool DataCache::judgeValid(const Instance& value) {
// 默认实现
std::cout << "judge_valid::default implementation" << std::endl;
return value.Isvalid();
}
// 定期更新数据的线程函数
void DataCache::updateThreadFunc(int intervalMs) {
while (!m_thread_stopFlag) {
// 从外部获取新数据
Instance value = fetchValueFromExternalSource();
// 判断数据是否异常,若异常则不更新缓存
if (false == judgeValid(value)) {
continue;
}
// 更新到缓存中
updateValue(value);
// 等待一段时间后再次更新
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
}
}
// end
}
cache.h
//
// Created by hanhandi on 2023/3/14.
//
#ifndef UNTITLED_Cache_H
#define UNTITLED_Cache_H
#include <thread>
#include <chrono>
#include <memory>
#include <stdio.h>
#include <string.h>
#include <mutex>
#include <random>
#include <sstream>
#include <iostream>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
namespace Cache {
// 若作为atomic,类需要保证trivially copyable
class Instance {
public:
Instance();
explicit Instance(const std::string& str);
bool Isvalid() const;
private:
char m_ip[16];
};
// 数据缓存类 不可继承
class DataCache final
{
public:
// 获取单例实例
static DataCache& instance(key_t key) {
static DataCache inst(key);
return inst;
}
// 禁止拷贝与赋值
DataCache(const DataCache&) = delete;
DataCache& operator=(const DataCache&) = delete;
Instance get();
inline key_t getKey() { return m_shm_key; }
private:
DataCache(key_t key);
~DataCache();
Instance fetchValueFromExternalSource() const;
bool serializeInstance(const Instance& obj);
bool deserializeInstance(Instance& obj);
void updateValue(const Instance& value);
bool judgeValid(const Instance& value);
void updateThreadFunc(int intervalMs);
using CacheArray = std::array<Instance, 2>; // 缓存数组类型定义
CacheArray m_cache; // 双缓冲缓存数组
size_t m_readIndex; // 可读部分索引
size_t m_writeIndex; // 可写部分索引
std::thread m_updateThread; // 更新数据的线程
bool m_thread_stopFlag; // update_thread的停止标志
std::mutex m_mtx; // lock
static const size_t SHM_SIZE = sizeof(Instance);
key_t m_shm_key;
};
// end
}
#endif //UNTITLED_Cache_H
main.cpp
//
// Created by hanhandi on 2023/3/14.
//
#include <iostream>
#include <unistd.h>
#include <sys/ipc.h>
#include "Cache.h"
// 根据当前进程名生成shm_key
key_t gen_shm_key() {
// 定义一个字符数组用于存储进程名称
char szProcessName[1024];
// 并将该路径作为进程名称返回
// 使用readlink函数读取/proc/self/exe符号链接指向的路径
ssize_t count = readlink("/proc/self/exe", szProcessName, sizeof(szProcessName));
if (count != -1)
{
szProcessName[count] = '\0';
// std::cout << "Process name: " << szProcessName << std::endl;
}
key_t key = ftok(szProcessName, 'R');
if (key == -1) {
perror("ftok error");
return -1; // 失败处理
}
std::cout << "key: " << key << std::endl;
return key;
}
int main()
{
key_t shm_key = gen_shm_key();
Cache::DataCache& Cache = Cache::DataCache::instance(shm_key);
// 模拟读取
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i++) {
auto res = Cache.get();
// std::this_thread::sleep_for(std::chrono::milliseconds(10));
usleep(100000);
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed_time = end - start;
std::cout << "Elapsed time: " << elapsed_time.count() << " seconds." << std::endl;
printf("end\n");
std::cout << std::hex << Cache.getKey() << std::endl;
}
makefile
CC = g++
CFLAGS = -Wall -Wextra -std=c++11 -lrt
SRCS = demo.cpp Cache.cpp
OBJS = $(SRCS:.cpp=.o)
all: demo
demo: $(OBJS)
$(CC) $(CFLAGS) $^ -o $@
%.o: %.cpp Cache.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o demo