召回算法中有一个叫做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
@classmethod
def instance(cls, *args, **kwargs):
with Singleton._instance_lock:
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance