c# ArrayPool 和对象池在高并发下的应用

千丽酱_7730

千丽酱_7730

2026-01-13

724人浏览

原创

ArrayPool比new T[n]更适合高并发场景,因其通过Rent/Return复用数组减少GC压力;但需严格配对调用且归还大小须匹配桶容量,否则静默失效。

c# arraypool 和对象池在高并发下的应用

ArrayPool 为什么比 new T[n] 更适合高并发场景

因为 ArrayPool.Shared.Rent() 复用内存块,避免频繁触发 GC;而 new T[n] 每次都分配新数组,在高并发下会快速堆积大量短期存活的中大型数组(尤其是 byte[]、int[]),导致 Gen 0 频繁回收,甚至诱发 Gen 1/2 收集,明显拖慢吞吐。

实际压测中,当每秒分配上万次 4KB–64KB 数组时,ArrayPool 可降低 GC 时间 70%+,但前提是必须 及时归还——漏调 Return() 会导致池子“饿死”,后续 Rent() 被迫退化为 new,反而更糟。

  • 只对中等大小(约 1KB–1MB)、生命周期短(毫秒级)的数组收益最大;太小(如 int[4])用栈变量或 Span 更合适,太大(如 >2MB)池子默认不缓存(受 maxArrayLength 限制)
  • ArrayPool.Create(minLength, maxLength) 可定制池子行为,比如设置 maxArraysPerBucket = 50 防止单个桶无限膨胀
  • 归还时传 clearArray: true 可清零内容(防敏感数据残留),但有性能开销,非必要不启用

自定义对象池(ObjectPool)和 ArrayPool 的关键区别

ObjectPool(来自 Microsoft.Extensions.ObjectPool)适用于任意引用类型对象复用,而 ArrayPool 专用于数组。两者底层都维护链表或栈式缓存,但 ObjectPool 必须提供 IPooledObjectPolicy 来控制创建、验证、清理逻辑,灵活性更高,也更容易出错。

常见误用是把带状态的对象(如未重置字段的 HttpRequestContext)直接塞进池子,下次取出时残留旧状态引发 bug。

  • 必须实现 IPooledObjectPolicy.Create() 和 IPooledObjectPolicy.Return(T obj),后者要负责重置所有可变字段(如 obj.Reset())
  • 池子容量默认无上限,需通过 MaximumRetained 限制缓存数量,否则内存持续增长
  • 不要在 Return() 中抛异常,否则对象会被丢弃,池子缓慢泄漏

高并发下 ArrayPool.Return() 调用失败的典型表现

最常被忽略的是:当归还的数组长度超过池子当前桶支持的最大长度(例如池子按 1024、2048、4096 分桶,却归还了 5000 字节的 byte[]),Return() 会静默失败——数组直接被 GC 回收,池子不报错也不警告。

deepstream-dev
deepstream-dev

使用 Python pyservicemaker API 进行 NVIDIA DeepStream SDK 开发。在构建视频分析流水线、基于 GStreamer

下载

这会导致你以为“用了池子就万事大吉”,实则部分请求仍在走 new 路径,压测时 GC 峰值忽高忽低,难以定位。

  • 用 ArrayPool.Shared.GetMaxSize() 查看当前池最大支持长度(.NET 6+ 默认 1MB)
  • 租用前先估算所需大小,避免跨桶;或用 ArrayPool.Create(maxArrayLength: 1024 * 1024 * 2) 扩容
  • 开启 DOTNET_gcServer=1 + DOTNET_gcConcurrent=1 确保服务端 GC 行为稳定,避免工作站 GC 在高并发下频繁暂停

一个安全的 ArrayPool 使用模板(C#)

核心原则:租用 → 使用 → 归还,三步必须成对出现,且归还必须放在 finally 或 using 中。以下是最小可靠模式:

public static async Task ProcessRequest(Stream input)
{
    byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
    try
    {
        int bytesRead = await input.ReadAsync(buffer, CancellationToken.None);
        // ... 处理 buffer 数据
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer);
    }
}</byte></byte>

注意:不要把 buffer 存到类字段、闭包或异步状态机里——归还后内存可能已被复用,再读就是脏数据。如果必须跨 await 使用,要么改用 Memory + ToArray()(代价是复制),要么改用对象池管理整个处理上下文。

真正难的不是写对这几行代码,而是确保整个调用链(包括所有异常分支、取消路径、嵌套异步)都覆盖归还逻辑。漏一次,就可能让池子在高负载下逐渐失效。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7579

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3585

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

4748

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

3165

10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

2023.07.18

7067

5

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2023.08.10

2908

6

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

2023.07.18

7067

5

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2023.08.10

2908

6

go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

2025.07.29

5233

19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程