首页 > 编程知识 正文

多线程动态分配内存,多线程跟内存关系

时间:2023-05-05 17:16:12 阅读:163157 作者:3311

第一层:线程缓存(线程缓存) ) ) ) ) ) ) ) ) ) ) ) )。

1.thread cache是内存池中的第1层高速缓存。

该层次缓存主要解决的问题是并发状态下锁竞争的高效问题。

线程不需要在这里锁定。 每个线程都有自己的cache。 也就是说,这是这个项目同时有效进行的地方。

如何确保每个线程都有自己的线程缓存?

为了避免锁定带来的效率,Thread Cache使用threadlocalstorage(TLS )来存储每个线程的本地Thread Cache指针。 这样,即使Thread Cache申请释放内存,也不需要锁定。

因为每个线程都有自己的全局变量。

TLS分为静态和动态两种。

静态TLS直接定义

动态TLS是通过调用系统的API创建的。 这个项目使用的是静态TLS

3359 blog.csdn.net/evils words/article/details/8191230

3359 blog.csdn.net/yusi Guyuan/article/details/22938671

2.

从Thread Cache申请内存:

在存储器申请size=64k的情况下,通过Thread Cache进行存储器申请,

计算size在自由链表中的位置,

如果自由链表中有内存对象,则为、

直接从freelist [ I ] pop对象,然后单击,

时间的复杂性是o(1),没有摇滚竞争。

如果FreeList[i]中没有对象,则为、

从Central Cache中批量获取一定数量的对象,

插入到自由链表中,返回对象。

释放Thread Cache的内存:

如果释放的内存小于64k,请将内存放回Thread Cache。

计算size在自由链表中的位置,并将对象推送到FreeList[i]。

如果链表的长度太长

这意味着,当超过一次最大限制时,Central Cache将回收部分内存对象。

第:层中央缓存

1.central cache是内存池中的第2层缓存。

这一层缓存主要解决的问题是内存分配不均问题。

在设计thread cache时,我们利用TLS使每个线程独占共享一个thread cache,尽管这样解决了内存碎片的问题,但还引起了另一个问题。 那是存储器资源分配的不平衡。 当某个线程释放和释放大量内存时,该线程中的thread cache必须存储大量的空闲内存资源,这些资源不能被其他线程使用,当其他线程需要使用内存资源时,可能无法使用更多的内存资源为了解决这个问题,有了central cache的设计。

central cache的主要功能:定期回收thread cache中的内存资源,避免了一个或多个线程占用大量内存资源,而其他线程内存资源不足的问题。 在多个线程上更均衡内存资源的分配。

2 .第2层是Central Cache,这里是所有线程共享的。Central Cache是有竞争的,所以在这里取内存对象时需要加锁,但可以将加锁的力控制在较小的程度。

为了确保全局唯一的Central Cache,可以将该类设计为单实例模式

单实例模式采用饥汉模式,避免高合并下资源的竞争

3.Central Cache发挥着承上启下的作用

可以为来自Thread Cache的每个线程分配内存对象,并管理线程返回的内存。

为此,你必须把页面缓存交给自己填满的内存块,并在自己需要的时候向页面缓存申请

4.Central Cache的本质是由散列映射的Span对象的自由双向链表构成的Span对象的大小为固定的4K大小(32位以下4K 64位以下8K ),但中央缓存数组的各个元素, 由于指定了1个Span被分割为存储块的大小(例如,最初的8字节、第2个16字节等),所以可安装的存储块数不同

5.Central Cache的本质是,由散列映射的Span对象的自由双向链表构成的Span对象的大小为固定的4k (32位以下4K 64位以下8K ),但中央缓存阵列的各个元素, 由于指定了1个Span被分割为存储块的大小(例如,最初的8字节、第2个16字节等),所以可安装的存储块数不同

从central Cache申请内存:

Thread Cache不满足线程的内存申请时,向Central Cache批量申请内存对象。 例如线程申请16bytes的内存,但此时Thread Cache中16bytes以上的已经没有了,所以此时向cantral申请Central Cache,在16bytes处获得spa

n 给thread cache,这个过程是需要加锁的。

当左边步骤中,向Central Cache中申请发现16bytes往后的span结点全空了时,则将空的span链在一起,然后向Page Cache申请若干以页为单位的span对象,比如一个3页的span对象,然后把这个3页的span对象切成3个一页的span对象 放在central cache中的16bytes位置可以有三个结点,再把这三个一页的span对象切成需要的内存块大小 这里就是16bytes ,并链接起来,挂到span中。

6.向central cache中释放内存

释放内存:

当Thread Cache过长或者线程销毁,则会将内存释放回Central Cache中的,

比如Thread cache中16bytes部分链表数目已经超出最大限制了 则会把后面再多出来的内存块放到central cache的16bytes部分的他所归属的那个span对象上.

此时那个span对象的usecount就减一位

当_usecount减到0时则表示所有对象都回到了span,则将Span释放回Page Cache,Page Cache中会对前后相邻的空闲页进行合并。

注意由span对象划分出去的内存块和这个span对象是有归属关系的

所以由thread cache归还释放某个内存(比如16bytes)应该归还到central cache的16bytes部分的他所归属的那个span对象上

怎么才能将Thread Cache中的内存对象还给它原来的span呢?

答:可以在Page Cache中维护一个页号到span的映射,当Page Cache给Central Cache分配一个span时,将这个映射更新到

第三层:Page Cache(即页缓存)

0.page cache的主要功能就是回收central cache中空闲的span,并且对空闲的span进行合并,合并成更大的span,当需要更大内存时,就不需要担心因为内存被切小而无法使用大内存的情况了,缓解了内存碎片的问题。而当central cache中没有可用的span对象时,page cache也会将大的内存切成需要大小的内存对象分配给central cache。

page cache中的内存是以页为单位储存和分配的。

1.存储的是以页为单位存储及分配的(中心缓存数组每个元素表示链表中span的页数),

中心缓存没有span时(所有的span链表都空了),从页缓存分配出一定数量的页,并切割成定长大小的小块内存(在中心缓存中对应的字节数 ),分配给Central Cache。Page Cache会回收Central Cache满足条件的Span(使用计数为0,即span结点满足一页)对象,并且合并相邻的页(根据页ID相邻),组成更大的页,缓解内存碎片的问题。

可以理解为central cache中的span和page cache中是不一样的

central cache中的span只代表一页 然后被分解成了小的内存块链接而成的链表

page cache中的span是有多页组成分的

2.Page cache是一个以页为单位的span自由链表。

为了保证全局只有唯一的Page cache,这个类可以被设计成了单例模式。

本单例模式采用饿汉模式。

3.向page cache申请内存

当Central Cache向page cache申请内存时,

当thread cache向Central Cache中申请,发现Central Cache16bytes往后的span结点全空了时,则将空的span链在一起,然后向Page Cache申请若干以页为单位的span对象,比如一个3页的span对象,Page Cache先检查3页的那个位置有没有span,如果没有则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是3page,3page后面没有挂span,则向后面寻找更大的span,假设在10page位置找到一个span,则将10page span分裂为一个3page span和一个7page span。

如果找到128 page都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式申请128page span挂在自由链表中,再重复1中的过程。

然后把这个3页的span对象切成3个一页的span对象 放在central cache中的16bytes位置可以有三个结点,再把这三个一页的span对象切成需要的内存块大小 这里就是16bytes ,并链接起来,挂到span中。

4.向page cache中释放内存

当Thread Cache过长或者线程销毁,则会将内存释放回Central Cache中的,

比如Thread cache中16bytes部分链表数目已经超出最大限制了 则会把后面再多出来的内存块放到central cache的16bytes部分的他所归属的那个span对象上.

此时那个span对象的usecount就减一位

当_usecount减到0时则表示所有对象都回到了span,则将Span释放回Page Cache,Page Cache中会依次寻找span的前后相邻pageid的span,看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少内存碎片。

5.page cache中最重要的就是合并了,在span结构中,有_pageid(页号)和_pagequantity(页的数量)两个成员变量,通过这两个成员变量,同时再利用unordered_map建立每一个页号到对应span的映射,就可以通过页号找到对应span进行合并了。

那么页号又是如何来的呢?当我们通过VirtualAlloc(Windows环境下是VirtualAlloc,Linux下使用brk或者mmap)直接向系统申请内存时,都是以页为单位的内存,而在32位机器下,一页就是4K。所以从0开始每一页的起始地址都是4K的整数倍,那么只需要将申请到的内存地址左移12位就可以得到相应的页号了,而通过页号也可以计算每一页的起始地址,只需要将地址右移12位即可。

具体合并的过程是这样的:假如现在有一个PageID为50的3页的span,有一个PageID为53的6页的span。这两个span就可以合并,会合并成一个PageID为50的9页的span,然后挂在9页的SpanList上。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。