召回算法中有一个叫做I2I的召回方式,给定一个Item,返回跟这个Item相似的topK个Item,这种相似关系一般在离线就计算好了,以KV的方式存储下来
<item1, [item2, item3,item4]>
<item2, [item1, item5,item6]>
...
我们使用的时候也加载成kv的格式就可以了
1. 基础实现
我们要提供这样一个数据,同样需要定义一个接口
using KvPair = std::unordered_map<std::string, std::vector<std::string>>;
class Kv{
public:Kv() = default;void update(const std::string& kvPath) {auto kv = std::make_shared<KvPair>();kv->emplace("item1", std::vector<std::string>{"item1", "item2", "item3"});kv->emplace("item2", std::vector<std::string>{"item1", "item4"});kv->emplace("item3", std::vector<std::string>{"item1"});{// 使用 lock_guard 锁定互斥锁std::lock_guard<std::mutex> lock(mtx);m_kv.swap(kv);}}std::vector<std::string> getTopK(const std::string& item) {auto iter = m_kv->find(item);if(iter != m_kv->end()) {return iter->second;}return std::vector<std::string>();}~Kv() = default;
private:std::shared_ptr<KvPair> m_kv = std::make_shared<KvPair>();// 创建一个互斥锁std::mutex mtx;
}
我们实现了两个方法,一个是数据更新的方法,一个是获取TopK的方法。客户端调用也非常的简单
auto kv = new Kv();
Kv->update('kv.pb');
auto items = Kv->getItems("item1");
2. 多处调用
现在有好几个地方都会调用这份kv数据,我们简单实现一下,第一个是test1.cpp
文件
// test1.cpp
void test1() {auto kv = new Kv();Kv->update('kv.pb');auto items = Kv->getItems("item1");
}
第二个地方是test2.cpp
文件
// test2.cpp
void test2() {auto kv = new Kv();Kv->update('kv.pb');auto items = Kv->getItems("item1");
}
这么实现的问题非常的明显,就是每个地方调用都需要加载一次数据,但是这份数据不会发生变化,谁调用都是一样的。
3. 单例模式
一个简单的想法就是让这个类对象只有一个,首先构造函数不能暴露出来,不然就可以定义多个对象了
class Kv{
private:// 构造函数和析构函数都定义为private,防止被外部调用Kv() = default;~Kv() = default;// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值Kv(const Kv &kv) = delete;const Kv &operator=(const Kv &kv) = delete;
public:// 类方法,类直接调用static Kv *GetInstance();
}
问题是构造函数都变成私有的了,该如何实例化这个对象呢?我们可以定义一个函数来代替构造函数,在这个函数里面我们设置一个标识符,表示这个对象是否已经初始化过了,如果初始化过了,就返回之前构造出来的对象,否则就进行构造初始化
Kv* Kv::kvInstance{nullptr};
std::mutex Kv::mutexKvInstance;
Kv* Kv::GetInstance()
{std::lock_guard<std::mutex> lock(mutexKvInstance);if (kvInstance== nullptr) // 如果是nullptr说明还没有被初始化过{kvInstance= new Kv();}return kvInstance; // 返回唯一的对象
}
还有一种更有有趣的实现方法,极力推荐
// 注意:不能返回指针的引用,否则存在外部被修改的风险!
Kv& Kv::GetInstance()
{/*** 局部静态特性的方式实现单实例。静态局部变量只在当前函数内有效,其他函数无法访问。* 静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。*/static Kv kv;return kv;
}
4. 单例模式python
import threading
class Singleton(object):_instance_lock = threading.Lock()def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instance