高并发内存池------内存释放

发布于:2025-05-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

 一、将一定数量的对象释放到span跨度

 

// 将一定数量的对象释放到span跨度
    void ReleaseListToSpans(void *start, size_t size)
    {
        size_t index = Sizeclass::Index(size);
        _spanLists[index]._mtx.lock();

        while (start)
        {
            void *next = NextObj(start);

            Span *span = PageCache::GetInstance()->MapObjectToSpan(start);
            NextObj(start) = span->_freeList;
            span->_freeList = start;
            span->_useCount--;

            // 说明span切分出去的小块内存都回来了
            // 这个span就可以再回收给pagecache,pagecache可以再尝试进行前后页的合并、
            if (span->_useCount == 0)
            {
                _spanLists[index].Erase(span);
                span->_freeList = nullptr;
                span->_next = nullptr;
                span->_prev = nullptr;

                // 释放span给pagecache时,使用pagecache的锁就好了
                // 解掉桶锁
                _spanLists[index]._mtx.unlock();

                PageCache::GetInstance()->_pageMtx.lock();
                PageCache::GetInstance()->ReleaseSpanToPageCache(span);
                PageCache::GetInstance()->_pageMtx.unlock();

                _spanLists[index]._mtx.lock();
            }
            start = next;
        }
        _spanLists[index]._mtx.unlock();
    }

        从代码来看,ReleaseListToSpans 函数的功能是将一定数量的对象释放到 Span 中,并且在 Span 的使用计数为 0 时,将 Span 释放回 PageCache。这个函数涉及多线程同步(通过锁)和资源管理。以下是对代码的分析和一些需要注意的地方:

1. 代码分析

1.1 锁的使用
  • _spanLists[index]._mtx.lock()_spanLists[index]._mtx.unlock() 用于保护 _spanLists[index] 的操作,确保线程安全。

  • PageCache::GetInstance()->_pageMtx.lock()PageCache::GetInstance()->_pageMtx.unlock() 用于保护 PageCache 的操作,确保线程安全。

1.2 对象释放逻辑
  • 遍历对象链表,将每个对象加入到对应的 Span_freeList 中。

  • 每次释放一个对象时,span->_useCount 减 1。

  • 如果 span->_useCount 为 0,说明该 Span 的所有对象都已释放,可以将 Span 释放回 PageCache

1.3 锁的嵌套
  • 在释放 Span 时,先解锁 _spanLists[index]._mtx,然后加锁 PageCache::GetInstance()->_pageMtx,最后再次加锁 _spanLists[index]._mtx

  • 这种锁的嵌套需要特别小心,否则可能导致死锁或竞态条件。

2. 代码优化和改进建议

2.1 锁的优化
  • 减少锁的持有时间:尽量减少锁的持有时间,以提高性能。

  • 避免锁的嵌套:如果可能,尽量避免锁的嵌套。如果必须嵌套,确保嵌套顺序一致,避免死锁。

2.2 错误处理
  • 空指针检查:在访问指针(如 span)之前,确保指针不为空。

  • 异常安全:确保在发生异常时,锁能够正确释放。

2.3 代码可读性
  • 注释:增加注释,解释每个步骤的目的。

  • 函数拆分:将复杂的逻辑拆分成多个函数,提高代码可读性。

二、 释放对象时,链表过长时,回收内存回到中心缓存

 // 释放对象时,链表过长时,回收内存回到中心缓存
    void ListTooLong(FreeList& list, size_t size)
    {
        void* start = nullptr;
        void* end = nullptr;

        list.PopRange(start, end, list.MaxSize());

        CentralCache::GetInstance()->ReleaseListToSpans(start, size);
    }

三、释放空闲span到pagecache,并合并相邻的span

 //释放空闲span到pagecache,并合并相邻的span
    void ReleaseSpanToPageCache(Span* span)
    {
        //对span前后的页尝试进行合并,缓解内存碎片的问题
        while (1)
        {
            PAGE_ID prevId = span->_pageId - 1;
            auto ret = _idSpanMap.find(prevId);
            //前面的页号没有,不合并
            if(ret == _idSpanMap.end())
            {
                break;
            }
            //前面相邻页的span在使用,不合并了
            Span* prevSpan = ret->second;
            if(prevSpan->_isUse == true)
            {
                break;
            }
            //合并后超过128页的span没办法管理,不合并
            if(prevSpan->_n + span->_n > NPAGES - 1)
            {
                break;
            }
            span->_pageId = prevSpan ->_pageId;
            span->_n += prevSpan->_n;

            _spanLists[prevSpan->_n].Erase(prevSpan);
            delete prevSpan;
        }

        //向后合并
        while(1)
        {
            PAGE_ID nextId = span->_pageId + span->_n;
            auto ret = _idSpanMap.find(nextId);

            if(ret == _idSpanMap.end())
            {
                break;
            }
            Span* nextSpan = ret->second;
            if(nextSpan->_isUse == true)
            {
                break;
            }
            if(nextSpan->_n + span->_n > NPAGES - 1)
            {
                break;
            }

            span->_n += nextSpan->_n;

            _spanLists[nextSpan->_n].Erase(nextSpan);
            delete nextSpan;
        }
        _spanLists[span->_n].PushFront(span);
        span->_isUse = false;
        _idSpanMap[span->_pageId] = span;
        _idSpanMap[span->_pageId + span->_n - 1] = span;
    }

        这段代码的功能是将一个空闲的 Span 释放到 PageCache 中,并尝试合并相邻的 Span,以减少内存碎片。以下是对代码的详细分析和改进建议。

1. 代码分析

1.1 向前合并逻辑
  • prevId:计算当前 Span 前一个页面的 ID。

  • _idSpanMap.find(prevId):查找前一个页面的 Span

  • 合并条件

    • 如果前一个 Span 未使用(prevSpan->_isUse == false)。

    • 合并后的 Span 大小不超过 NPAGES - 1

  • 合并操作

    • 更新当前 Span_pageId_n

    • _spanLists 中移除前一个 Span

    • 删除前一个 Span

1.2 向后合并逻辑
  • nextId:计算当前 Span 后一个页面的 ID。

  • _idSpanMap.find(nextId):查找后一个页面的 Span

  • 合并条件

    • 如果后一个 Span 未使用(nextSpan->_isUse == false)。

    • 合并后的 Span 大小不超过 NPAGES - 1

  • 合并操作

    • 更新当前 Span_n

    • _spanLists 中移除后一个 Span

    • 删除后一个 Span

1.3 最终操作
  • 将当前 Span 插入到 _spanLists 中。

  • 更新 _idSpanMap,确保当前 Span 的起始和结束页面 ID 都指向该 Span


网站公告

今日签到

点亮在社区的每一天
去签到