目录

  • 1、双缓冲区 读写分离
  • 2、后台线程定时更新数据
  • 3、类设计
  • 完整代码
  • cache.cpp
  • cache.h
  • main.cpp
  • makefile



读写分离的双缓冲buffer有以下好处:

  1. 提高了并发读写的效率:在多线程环境下,读写操作是相互竞争的,读写分离的双缓冲buffer可以有效地减少读写之间的竞争,提高并发读写的效率。
  2. 减少了数据的拷贝:双缓冲buffer可以将数据从写缓冲区直接拷贝到读缓冲区,避免了中间数据拷贝的过程,提高了数据读写的效率。
  3. 提高了数据的安全性:双缓冲buffer可以避免读写操作的交叉,从而保证了数据的安全性。
  4. 提高了系统的稳定性:双缓冲buffer可以避免读写操作的阻塞,从而提高了系统的稳定性和可靠性。

1、双缓冲区 读写分离

单缓冲存在读写冲突的问题,即同时进行读写操作时容易出现数据不一致的情况。而采用双缓冲则可以让读写操作互不干扰。

具体表现

读取操作只会访问当前可读的缓存,写入操作只会访问当前可写的缓存。

每次更新数据时,先修改写缓存值并将其序列化到共享内存中,然后交换读写部分索引,使得读部分开始访问最新可用的数据。

流程如下:

java 双buffer实现 双buffer 线程安全_#include


互斥阶段

写线程的交换索引这一步需要加锁,锁粒度很小

java 双buffer实现 双buffer 线程安全_双缓冲buffer_02

读线程读取的时候也需要加锁防止写线程修改索引,锁粒度也仍然很小

java 双buffer实现 双buffer 线程安全_#include_03

2、后台线程定时更新数据

提供了一个定期更新数据的线程函数,通过调用SDK获取新数据,

并通过 judge_valid 判断新数据是否有效,如果有效,则调用 update_value 更新到可写缓存中,并更新共享内存。

最后,在析构函数中等待更新线程结束。

java 双buffer实现 双buffer 线程安全_#include_04

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