Span和Memory本身不提升并发性能,它们是为避免堆分配、减少GC压力而设计的内存访问工具,仅在短生命周期、只读或单写多读场景中间接支撑高并发。
和 memory
这是最关键的误判点:Span 和 Memory 不是并发原语,它们不带锁、不保证线程安全、也不参与调度。用错场景反而引发 System.IndexOutOfRangeException 或 ObjectDisposedException(尤其在跨线程传递 Span 时)。
它们的价值在于:避免堆分配、减少 GC 压力、绕过数组边界检查(JIT 优化后),从而间接支撑高并发吞吐——前提是你的并发瓶颈真正在内存分配或拷贝上。
典型有效场景集中在「短生命周期、高频率、只读或单线程写+多线程读」的数据处理链路中:
Span 后直接切片解析,不 new byte[])Memory 持有池化 ArrayPool.Shared 分配的数组,避免每次接收都 new)Span 拼接结构化字段,配合 IBufferWriter 直写到输出流)Memory 可跨线程传递;Span 绝对不可)Span 是 ref-like 类型,绑定栈帧或特定对象生命周期,**不能作为字段、不能存储在堆对象中、不能跨 await 边界、更不能在线程间传递**。试图这么做会触发编译错误或运行时 System.ArgumentException: Span。
Memory 才是为异步/并发设计的轻量包装器,它可安全持有并传递,但需注意:
new byte[1024])、栈内存(stackalloc,但无法跨方法返回)或本机内存(NativeMemory.Allocate)ArrayPool.Shared.Rent() 创建 Memory,必须确保归还(Return()),否则池耗尽会导致新分配退化为 new byte[]
Memory 安全;但若某线程在写底层数组(比如通过 Memory.Span 修改),其他线程读就构成竞态——这和普通数组一样,需额外同步以下是一个 Socket 接收 + 复用缓冲区的典型模式,体现 Memory 如何降低分配压力:
private static readonly ArrayPool_pool = ArrayPool .Shared; public async Task ProcessRequestAsync(Socket socket) { var buffer = _pool.Rent(8192); try { var memory = new Memory (buffer); int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None); // ✅ 安全:只读切片,不修改 buffer 内容 var payload = memory.Slice(0, bytesRead); ParseRequest(payload); // 接收端逻辑,入参为 ReadOnlySpan } finally { // ✅ 必须归还,否则池泄漏 _pool.Return(buffer); } }
注意三点:
memory.Slice(...) 存成类字段或传给其他
Memory 不管理生命周期,只依赖你正确归还底层数组ParseRequest 内部保存 ReadOnlySpan 到字段——它会随栈帧销毁而失效ToArray() 或写入目标对象),不能依赖原始 Span/Memory
真正卡并发性能的,往往不是 Span 或 Memory 用得够不够“炫”,而是有没有把它们嵌进正确的内存生命周期里——池没配对、切片越界、跨线程误传 Span,比不用它们还容易出问题。