fileshare不保证公平性,仅控制文件打开权限;真正的并发读写协调需用readerwriterlockslim等上层机制配合统一锁实例和规范释放。

FileShare 参数不等于公平性,FileStream 本身不保证读写顺序
很多人以为只要用 FileShare.ReadWrite 就能“公平”处理并发访问,其实这是误解。.NET 的 FileStream 底层依赖操作系统文件句柄,而 Windows/Linux 对文件共享的调度是无序的——先抢到句柄的线程/进程就上,不管它是要读还是写。所谓“公平”,必须靠上层逻辑控制。
-
FileShare只决定能否同时打开,不干预后续 I/O 的执行时机或优先级 - 多个线程调用
new FileStream(...)时,构造函数返回不代表已获得磁盘访问权,真正阻塞可能发生在第一次Read()或Write() - 如果写操作耗时长(比如写几百 MB),读线程可能无限期等待,这不是公平,是饥饿
用 ReaderWriterLockSlim 做内存协调,但注意它不锁文件本身
真正的公平读写锁得自己组装:用 ReaderWriterLockSlim 管理“谁可以发起文件操作”,再配合实际的 FileStream 操作。但它只锁代码路径,不锁磁盘,所以必须确保所有文件访问都经过同一把锁实例,且不能绕过。
- 必须在同一个作用域内创建并复用单个
ReaderWriterLockSlim实例,不能每次操作都 new 一个 - 读操作用
EnterReadLock()+TryEnterReadLock()配合超时,避免写线程长期被饿死 - 写操作务必用
EnterWriteLock(),且内部要尽快完成 —— 锁住期间其他读写全卡住 - 记得在
finally块里调用ExitReadLock()或ExitWriteLock(),否则锁泄漏会导致整个系统僵死
var rwLock = new ReaderWriterLockSlim();
// 读
rwLock.EnterReadLock();
try {
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
// ...读取
} finally { rwLock.ExitReadLock(); }
文件独占写 + 追加模式比“公平读写”更实用
多数场景下,与其费力模拟公平,不如换思路:让写操作走独占、原子、低延迟路径,读操作尽量缓存或降级。例如日志文件、配置快照这类典型用例,写入用 FileMode.Create + FileAccess.Write + FileShare.None,读取则从内存缓存或副本拿。
-
FileMode.Append虽然允许并发打开,但多线程写入仍可能交错(Windows 下尤其明显),不是线程安全的追加 - 真正安全的追加要用
FileStream.Lock()手动锁住文件区域,但跨进程无效,且容易死锁 - 如果必须支持高并发读+偶发写,建议用内存映射文件(
MemoryMappedFile)+ 自定义读写计数器,比纯 FileStream 更可控
跨进程公平?别指望 ReaderWriterLockSlim,得用命名同步原语
ReaderWriterLockSlim 是进程内锁,跨进程时完全失效。如果你的程序有多个实例(比如 Windows 服务 + GUI 工具同时操作同一文件),必须升级到系统级同步机制。
- 用
Mutex或Semaphore配合命名前缀(如"Global\MyApp_FileAccess"),注意权限问题,普通用户可能无法访问Global命名空间 -
EventWaitHandle可以做读写信号量,但需要你自己维护读计数器和写状态,容易出错 - 更稳的做法是引入轻量级本地 IPC(比如命名管道或
NamedPipeServerStream),把文件操作收口到一个守护进程里,其他进程只发请求
公平从来不是文件系统的默认属性,而是你对访问路径、资源粒度和错误容忍度权衡后的结果。最容易被忽略的一点:当多个线程都在等同一个文件锁时,它们的等待顺序取决于 OS 调度器,而不是你代码里的 EnterReadLock() 调用顺序。










