LazyInstance
是谷歌nbase
库里的模板类,主要作用是延迟创建实例
成员
类私有部分如下
1 | template<typename Type> |
将定义都展开一下
1 | typedef __w64 int32_t Atomic32; |
实际上它保存了两个值:
state_
类型是在win32是int类型,保存此对象的状态,值可以为上面的4中枚举状态instance_
在win32下是intptr_t类型,实际上存的是模板Type类型对象的地址
而DISALLOW_COPY_AND_ASSIGN
说明它禁止拷贝
实现
共有部分代码如下
1 | public: |
可以看出,他具体延迟创建的代码在Pointer函数中实现。大概流程如下:
- 通过
Acquire_Load
函数判断当前状态是否为已创建状态kCreated
,如果是,说明已经有实例,直接返回instance_
对象Acquire_Load
函数属于读屏障,不取cache的值,而是取主存的值,在多线程环境下保证了此成员变量为最新 - 如果当前状态是
kNone
,则更新为kCreating
,通过NoBarrier_CompareAndSwap
函数实现这个注释写的很明白:CompareAndSwap(CAS)是原子性操作。说明多线程同时跑到这个语句时,此函数保证后入者会得到最新的已经更改过的值,因此只有一个线程能够将1
2
3
4
5
6
7
8
9
10
11
12
13// Atomically execute:
// result = *ptr;
// if (*ptr == old_value)
// *ptr = new_value;
// return result;
//
// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value".
// Always return the old value of "*ptr"
//
// This routine implies no memory barriers.
Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
Atomic32 old_value,
Atomic32 new_value);kNone
状态改为kCreating
状态,另一个函数会在这之后原子操作,得到state为kCreating
- 如果state是
kNone
,则注意参与比较的是局部变量而非成员变量。经过上一步的顺序执行后,实际上到这里已经线程安全了
- 创建实例
- 退出注册(后面再说)
- 将成员状态
state_
更新为kCreated
Release_Store函数属于写屏障,让写到cache的数据写进主存,让其他线程可见
- 如果state不属于
kCreated
(而且2步骤得到的局部变量不是kNone
),说明有另一个线程重入并且在创建对象,等待创建好了即可
比较
1 | public: |
不存在没构造好懒对象就比较的情况,因此结合状态比较即可
删除
直接delete会造成内存泄漏,需要用OnExit函数
1 | private: |
LazyInstance
对象只能通过此函数进行删除,而此函数是私有的,所以LazyInstance
对象只能在程序结束时统一删除,并不能手动删除(不过LazyInstance
对象一般都是全局对象,很少会主动删除)
好处
- 全局变量延迟创建
- 相比于单例,
LazyInstance
对象可以有多份
关于内存屏障
学习这个之前我还不了解内存屏障,在翻源码时发现了一点小知识,先记录一下
1 | inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { |
上面是x86架构下msvc的内存屏障
1 | inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { |
这是x86下gcc的内存屏障
volatile
关键字在msvc跟gcc下都能保证编译期指令不被打乱,但是gcc的运行时内存屏障需要有一段汇编代码ATOMICOPS_COMPILER_BARRIER()
实现,而msvc通过volatile
关键字就可以实现了