0%

消息循环是nbase库比较重要的一个部分,也比较庞大,在这里先记录其重要的部分,也算一个索引,后面在详细记录实现部分
nbase将消息循环按照功能拆分成及部分

  • MessagePump 控制消息循环的开始跟停止
  • MessageLoop 消息循环中的消息队列实现

    MessagePump

    消息泵。驱动各种类型的消息循环,简单来说就是实现死循环的类
    它主要提供几个方法
  • Run 启动消息循环
  • Quit 结束消息循环
  • ScheduleWork 处理任务
  • ScheduleDelayedWork 处理延时任务

MessagePump主要充当CPU跟MessageLoop间中间人的角色。MessageLoop不用操心啥时候有时间片啥时候开始啥时候停止,而MessagePump也不用操心要执行什么任务

MessageLoop

消息循环。主要维护任务队列,提供接口供外界提交任务,提供任务给MessagePump执行。
简单的消息循环一定要提供几个方法

  • DoWork 处理普通任务
  • DoDelayedWork 处理延时任务
  • DoIdleWork 处理空闲任务

nbase库将任务分成这三种类型

谷歌nbase库提供了一种集合元素删除的方法,名为LazyEraser

优点

C++容器类一般都是调用erase方法删除元素,但是erase会导致迭代器失效。LazyEraser延迟了删除元素的时机,一次删除,提高了效率,控制了删除的时机

抽象基类

1
2
3
4
5
6
7
8
9
10
11
12
class LazyEraser
{
public:
LazyEraser() : lazy_erase_(0) {}
/* NOTE: calling 'ApplyLasyErase' increases the reference count, calling 'ReleaseLasyErase' decreases the reference count */
int AquireLazyErase() { return ++lazy_erase_; }
int ReleaseLazyErase() { assert(lazy_erase_ > 0); return --lazy_erase_; }
virtual void Compact() = 0;

protected:
int lazy_erase_;
};

基类LazyEraser维护了一个引用计数。定义了抽象方法Compact,用于主动删除元素

例子

nbase库里记录观察者的列表类ObserverList就是使用LazyEraser的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename ObserverType>
class ObserverList : public LazyEraser
{
public:
void AddObserver(ObserverType *observer)
{
assert(observer);
if (std::find(observers_.begin(), observers_.end(), observer) == observers_.end())
observers_.push_back(observer);
}

// ...

ObserverType* GetObserver(size_t index) const
{
if (index >= GetObserverCount())
return NULL;
return observers_[index];
}

size_t GetObserverCount() const { return observers_.size(); };

private:
std::vector<ObserverType *> observers_;
};

本质上ObserverList属于std::vector,在插入、取值、计数等方面与std::vector表现一致,区别在于删除方面。

1
2
3
4
5
6
7
8
9
10
11
void RemoveObserver(ObserverType *observer)
{
typename std::vector<ObserverType *>::iterator pos = std::find(observers_.begin(), observers_.end(), observer);
if (pos == observers_.end())
return;
/* this method may be called within a traversal of the list */
if (lazy_erase_ > 0) // 关键
*pos = NULL;
else
observers_.erase(pos);
}

调用RemoveObserver删除时如果此时引用计数不为0,那么容器不会调用std::vector::erase函数,只是仅仅将该地址置空。也就是说它并不会立即回收删除对象在容器里对应的内存(所以对应的迭代器还能正常使用),等到调用Compact函数,才会正常释放内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Compact()
{
if (lazy_erase_ > 0 || observers_.empty())
return;
ObserverType **src = &observers_[0];
ObserverType **target = src;
ObserverType **end = src + observers_.size();
/* fast compacting algorithm */
while (src < end)
{
if (*src != NULL)
{
if (target != src)
*target = *src;
target++;
}
src++;
}
observers_.resize(target - &observers_[0]);
}

改进

如果当引用计数为0的时候忘了调用Compact,那么岂不是会造成内存泄漏?这部分内存得等到析构的时候才能正常释放?
谷歌考虑到这种情况,使用RAII机制新增了AutoLazyEraser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class AutoLazyEraser
{
public:
AutoLazyEraser(LazyEraser* lazy_eraser) : ptr_(lazy_eraser)
{
if (ptr_)
ptr_->AquireLazyErase();
}

AutoLazyEraser(AutoLazyEraser &lazy_eraser)
{
if (lazy_eraser.ptr_)
lazy_eraser.ptr_->AquireLazyErase();
ptr_ = lazy_eraser.ptr_;
}

void operator=(AutoLazyEraser &lazy_eraser)
{
if (lazy_eraser.ptr_)
lazy_eraser.ptr_->AquireLazyErase();
ptr_ = lazy_eraser.ptr_;
}

~AutoLazyEraser()
{
if (ptr_)
{
if (ptr_->ReleaseLazyErase() < 1)
ptr_->Compact();
}
}

private:
LazyEraser *ptr_;
};

AutoLazyEraser类在析构时会自动调用一次Compact,这样就不怕泄露了

这个是谷歌nbase库里面的线程局部存储模板类

跨平台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct ThreadLocalPlatform
{
#if defined(OS_WIN)
typedef unsigned long SlotType;
#elif defined(OS_POSIX)
typedef pthread_key_t SlotType;
#endif

static void AllocateSlot(SlotType &slot);
static void FreeSlot(SlotType &slot);
static void* GetValueFromSlot(SlotType &slot);
static void SetValueInSlot(SlotType &slot, void *value);
};

// unix
void ThreadLocalPlatform::AllocateSlot(SlotType &slot)
{
int error = pthread_key_create(&slot, NULL);
assert(error == 0);
}

// win
void ThreadLocalPlatform::AllocateSlot(SlotType &slot)
{
slot = ::TlsAlloc();
assert(slot != TLS_OUT_OF_INDEXES);
}

这个类提供跨平台API选择,Windows主要通过::TlsAlloc实现,unix主要通过pthread_key_create实现

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
template<typename Type>
class ThreadLocalPointer
{
public:

ThreadLocalPointer() : slot_()
{
internal::ThreadLocalPlatform::AllocateSlot(slot_);
}

~ThreadLocalPointer()
{
internal::ThreadLocalPlatform::FreeSlot(slot_);
}

Type* Get()
{
return static_cast<Type*>(internal::ThreadLocalPlatform::GetValueFromSlot(slot_));
}

void Set(Type *ptr)
{
internal::ThreadLocalPlatform::SetValueInSlot(slot_, ptr);
}

private:
typedef internal::ThreadLocalPlatform::SlotType SlotType;
SlotType slot_;

DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer);
};
  • 很简单的贫血模式
  • 对目标类并不直接存储,而是记录其地址
  • 禁止拷贝,保证安全

LazyInstance是谷歌nbase库里的模板类,主要作用是延迟创建实例

成员

类私有部分如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<typename Type>
class LazyInstance
{
// ...
private:
enum
{
kNone = 0,
kCreating,
kCreated,
kDeleting,
};

base::subtle::Atomic32 state_;
base::subtle::AtomicWord instance_;

DISALLOW_COPY_AND_ASSIGN(LazyInstance);
}

将定义都展开一下

1
2
3
4
5
6
typedef __w64 int32_t Atomic32;
typedef intptr_t AtomicWord;

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)

实际上它保存了两个值:

  • state_
    类型是在win32是int类型,保存此对象的状态,值可以为上面的4中枚举状态
  • instance_
    在win32下是intptr_t类型,实际上存的是模板Type类型对象的地址

DISALLOW_COPY_AND_ASSIGN说明它禁止拷贝

实现

共有部分代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public:

LazyInstance() : instance_(NULL)
{
base::subtle::NoBarrier_Store(&state_, kNone);
}

~LazyInstance()
{
// |instance_| should be deleted by |OnExit|
//DCHECK(instance_ == 0);
}

Type& Get()
{
return *Pointer();
}

Type* Pointer()
{
using namespace base::subtle;

if (Acquire_Load(&state_) != kCreated)
{
Atomic32 state =
NoBarrier_CompareAndSwap(&state_, kNone, kCreating);
if (state == kNone)
{
// we take the chance to create the instance
instance_ = reinterpret_cast<AtomicWord>(new Type());
AtExitManager::RegisterCallback(OnExit, this);
Release_Store(&state_, kCreated);
}
else if (state != kCreated)
{
// wait, util another thread created the instance
while (Acquire_Load(&state_) != kCreated)
Thread::YieldThread();
}
}

return reinterpret_cast<Type *>(instance_);
}

可以看出,他具体延迟创建的代码在Pointer函数中实现。大概流程如下:

  1. 通过Acquire_Load函数判断当前状态是否为已创建状态kCreated,如果是,说明已经有实例,直接返回instance_对象

    Acquire_Load函数属于读屏障,不取cache的值,而是取主存的值,在多线程环境下保证了此成员变量为最新

  2. 如果当前状态是kNone,则更新为kCreating,通过NoBarrier_CompareAndSwap函数实现
    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);
    这个注释写的很明白:CompareAndSwap(CAS)是原子性操作。说明多线程同时跑到这个语句时,此函数保证后入者会得到最新的已经更改过的值,因此只有一个线程能够将kNone状态改为kCreating状态,另一个函数会在这之后原子操作,得到state为kCreating
  3. 如果state是kNone,则

    注意参与比较的是局部变量而非成员变量。经过上一步的顺序执行后,实际上到这里已经线程安全了

    1. 创建实例
    2. 退出注册(后面再说)
    3. 将成员状态state_更新为kCreated

      Release_Store函数属于写屏障,让写到cache的数据写进主存,让其他线程可见

  4. 如果state不属于kCreated(而且2步骤得到的局部变量不是kNone),说明有另一个线程重入并且在创建对象,等待创建好了即可

比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public:
bool operator ==(Type *object) const
{
switch (base::subtle::NoBarrier_Load(&state_))
{
case kNone:
return object == NULL;
case kCreating:
case kCreated:
return instance_ == object;
default:
return false;
}
}

不存在没构造好懒对象就比较的情况,因此结合状态比较即可

删除

直接delete会造成内存泄漏,需要用OnExit函数

1
2
3
4
5
6
7
8
private:
static void OnExit(void *lazy_instance)
{
LazyInstance<Type>* me =
reinterpret_cast<LazyInstance<Type>*>(lazy_instance);
delete reinterpret_cast<Type*>(me->instance_);
base::subtle::NoBarrier_Store(&me->instance_, 0);
}

LazyInstance对象只能通过此函数进行删除,而此函数是私有的,所以LazyInstance对象只能在程序结束时统一删除,并不能手动删除(不过LazyInstance对象一般都是全局对象,很少会主动删除)

好处

  • 全局变量延迟创建
  • 相比于单例,LazyInstance对象可以有多份

关于内存屏障

学习这个之前我还不了解内存屏障,在翻源码时发现了一点小知识,先记录一下

1
2
3
4
5
6
7
8
9
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
Atomic32 value = *ptr;
return value;
}

inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
*ptr = value; // works w/o barrier for current Intel chips as of June 2005
// See comments in Atomic64 version of Release_Store() below.
}

上面是x86架构下msvc的内存屏障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
Atomic32 value = *ptr; // An x86 load acts as a acquire barrier.
// See comments in Atomic64 version of Release_Store(), below.
ATOMICOPS_COMPILER_BARRIER();
return value;
}

inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
ATOMICOPS_COMPILER_BARRIER();
*ptr = value; // An x86 store acts as a release barrier.
// See comments in Atomic64 version of Release_Store(), below.
}

#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory")

这是x86下gcc的内存屏障

volatile关键字在msvc跟gcc下都能保证编译期指令不被打乱,但是gcc的运行时内存屏障需要有一段汇编代码ATOMICOPS_COMPILER_BARRIER()实现,而msvc通过volatile关键字就可以实现了

另外版本的实现

https://blog.csdn.net/leehark/article/details/6897858

Chrome浏览器源码的公共库nbase中,有一套用于检测对象生命周期的方法。主要使用C++标准库中的shared_ptr以及weak_ptr实现

阅读全文 »