您的位置:首页 > 编程语言 > C语言/C++

C++内存管理变革(8):No Lock(无锁)的GC Allocator

2008-03-05 16:25 232 查看
注:本文的内容已经过时。最重要的一点变化是:我们文章中提到BlockPool是可以在不同的Thread中共享的,这一点发生了变化,我们把BlockPool也做成线程一级了(BlockPool不再线程安全)。



---

C++内存管理变革(8):No Lock(无锁)的GC Allocator

许式伟
2008-3-5

引言

我们在前文已经引入了两个GC Allocator

AutoFreeAlloc (参见《最袖珍的垃圾回收器 - AutoFreeAlloc》)

ScopeAlloc (参见《通用型垃圾回收器 - ScopeAlloc》)

你可能已经注意到,这两个 GC Allocator 都是非线程安全的(确实有不少人向我反馈的这个“问题”)。不过,这其实是有意为之。下面我们解释为什么。

免费午餐已经结束

尽管我们可以使用的 CPU 频率已经越来越高,以至于我们不少人称这是一个“CPU计算能力过剩的时代”。但是,事实恰恰相反。免费午餐已经结束,软件在历史性地向并发靠拢

CPU性能提升在两年前就开始碰壁,但大多数人到了最近才有所觉察。大概在2003年初,一路高歌猛进的CPU时钟速度突然急刹车。受制于一些物理 学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题等,时钟速度的提升已经越来越难。从单个CPU角度来讲,莫尔定律已经不再适用了。

接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的:

1、超线程
2、多核
3、缓存

软件在历史性地向并发靠拢

随着多核趋势的明朗,对软件来说,这意味着一次巨变多核时代,注定要改变计算机发展历史。在我们还在努力学习OO方法论时,须不知,一场新的颠覆性的编程革命到来了。

这场编程革命是什么呢?那就是“并行编程”。也许你会说,不就是“CreateThread和锁”吗,我已经会了。但这是完全不同的“并行编程”风格,我们可以称之为“无锁并行编程”,也可以称之为“基于异步消息传递的并行编程模型”。

这些和GC Allocator有什么关系?

内存管理是程序语言中的最基础的设施。如果你长期做服务端的开发,一定知道,服务器性能调优的关键在于内存管理。为什么GC Allocator是No Lock(无锁)的?答案是:性能!

那么,为什么GC Allocator可以是No Lock(无锁)的?原因在于,我们并不推荐你在两个进程之间Share彼此的内存(也就是说,不能在两个线程之间Share一个GC Allocator)。

以ScopeAlloc为例:

class Thread1
{
private:
    std::BlockPool& m_recycle;
 
public:
    Thread1(std::BlockPool& recycle) : m_recycle(recycle)
    {
    }
 
    void operator()()
    {
        std::ScopeAlloc alloc1(m_recycle);
        ...
    }
};
 
class Thread2
{
private:
    std::BlockPool& m_recycle;
 
public:
    Thread2(std::BlockPool& recycle) : m_recycle(recycle)
    {
    }
 
    void operator()()
    {
        std::ScopeAlloc alloc2(m_recycle);
        ...
    }
};
 
int main()
{
    std::BlockPool recycle;
    boost::thread thread1(Thread1(recycle));
    boost::thread thread2(Thread2(recycle));
    thread1.join();
    thread2.join();
    return 0;
}


如该例子所示,我们推荐的实现方式是,每个线程有自己的私有内存分配器(GC Allocator),如上面的alloc1、alloc2。但他们可以共用同一个BlockPool。

BlockPool是线程安全的。之所以这样,是基于以下观念:

从逻辑的层次来讲,我们把组件分为两种,一种是系统级的,偏于计算机系统本身的抽象,如上面的BlockPool。它们从不直接由用户使用。所谓的 “无锁”编程,当然不是说不能用“锁”,只是把锁减少到最少。少到什么程度呢?少到只有系统级的组件才不得不用“锁”。而另一种组件是用户级的,偏于算法 逻辑的抽象,这类组件永远不要有“锁”。

现在,为什么GC Allocator没有锁的原因就很明了了:

内存分配器(GC Allocator)是用户级的概念,它只是算法逻辑的抽象。ScopeAlloc设计的妙处在于,把系统级的内存管理独立抽象到一个BlockPool类里,以将内存管理中的“锁”的代价减少到最低水平。

当然,两个GC Allocator也可以各自有自己的BlockPool,但是为了让一个Thread释放的内存可以立即被另一个Thread所使用,我们推荐你共享两个BlockPool。理论上,在一个应用程序中,只需要一个BlockPool实例是最好的。

参考:性能对比

AutoFreeAlloc, ScopeAlloc vs APR (apache portable runtime) pools (apr_pool_t)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: