高并发下 filestream.write 崩溃主因是内核句柄竞争、i/o 阻塞与磁盘寻道放大;streamwriter 非线程安全易致异常或数据错乱;推荐 concurrentqueue + 后台线程批量刷盘解耦生产消费,兼顾性能与稳定性。

为什么直接 FileStream.Write 在高并发下会崩
多个线程同时调用 FileStream.Write 写同一个文件,哪怕加了 lock,也会因频繁的 I/O 阻塞、内核句柄竞争和磁盘寻道放大延迟,吞吐迅速跌穿 1MB/s。更糟的是,StreamWriter 默认带缓冲但非线程安全,跨线程写入可能触发 ObjectDisposedException 或数据错乱。
- Windows 上对同一文件的并发
WriteFile调用会被串行化,本质仍是单点瓶颈 - 每次写入都触发一次系统调用,上下文切换成本在万级 QPS 下不可忽略
- 小块写入(如每条日志几十字节)会激增磁盘 I/O 次数,SSD 寿命和延迟双受损
用 ConcurrentQueue + 后台写入线程是最稳的起点
核心思路是解耦“生产”和“消费”:业务线程只往线程安全队列投递数据,由单一后台线程批量刷盘。这避免锁争用,也天然聚合小写请求。
- 用
ConcurrentQueue<string></string>或ConcurrentQueue<byte></byte>存原始数据,比BlockingCollection更轻量(后者含额外同步开销) - 后台线程用
Thread.Sleep(1)或SpinWait.SpinOnce()等待,别用BlockingCollection.Take()—— 它在空时仍会进内核态,增加延迟 - 攒够阈值(如 8KB 或 100 条)再刷盘,或每 10ms 强制 flush 一次,防消息积压过久
var queue = new ConcurrentQueue<byte[]>();
Task.Run(() => {
var buffer = new List<byte[]>(128);
while (!cancellationToken.IsCancellationRequested) {
if (queue.TryDequeue(out var item)) {
buffer.Add(item);
if (buffer.Sum(b => b.Length) >= 8192) Flush(buffer);
} else if (buffer.Count > 0) {
Flush(buffer);
} else {
Thread.Sleep(1); // 低频轮询,避免 CPU 空转
}
}
});
MemoryMappedFile 适合超大吞吐但代价高
当单机写入持续超过 500MB/s(如金融行情快照),纯队列模型可能因内存拷贝和 GC 压力成为瓶颈。MemoryMappedFile 可绕过 .NET 堆,让多线程直接写共享内存页,再由独立线程调用 FlushViewOfFile 刷盘。
Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。它虽然不是Linux系统核心的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Linux系统
- 必须用
FileOptions.RandomAccess | FileOptions.WriteThrough创建映射,禁用系统缓存,避免 double-buffering - 需自行管理写位置指针(
long类型原子变量),并处理跨页边界——写到页尾要手动切到下一页 - 崩溃恢复难:映射文件不保证落盘顺序,断电可能丢最后几页数据,必须配 CRC 校验和 checkpoint 机制
别忽略 FileOptions.Asynchronous 和 WriteAsync
如果写入目标是网络文件系统(如 SMB 共享)或云存储网关,同步 I/O 会卡死整个线程池。此时应改用异步流,但要注意陷阱:
-
FileStream必须用FileOptions.Asynchronous构造,否则WriteAsync内部仍走同步路径 - 不要在
async void方法里写日志,异常会静默丢失;统一用async Task并 await - 并发
WriteAsync到同一文件仍需锁(如SemaphoreSlim),但锁粒度可缩小到单次 write 调用,而非整条消息
真正难的不是堆砌技术,而是确定你的瓶颈在哪——是磁盘吞吐?CPU 序列化?还是网络延迟?先用 PerfView 抓一下 ThreadPool 和 IO 的热区,再决定上 MemoryMappedFile 还是老实用队列。







