C# 跨平台文件锁定 C#如何实现一个在Windows和Linux上都工作的顾问锁

1次阅读

linux flock与windows FileStream.lock均为advisory锁,但api行为差异大:linux重复flock不报错,windows重复lock抛ioexception;跨平台应统一用filestream+fileshare.none+异常捕获模拟advisory锁。

C# 跨平台文件锁定 C#如何实现一个在Windows和Linux上都工作的顾问锁

Linux 上 flock 和 Windows 上 FileStream.Lock 行为不一致

Linux 的 flock 是 advisory(顾问锁),依赖所有参与者主动检查;Windows 的 FileStream.Lock 默认是 mandatory(强制锁),但仅对同一句柄有效,跨进程时实际也退化为 advisory。两者底层语义接近,但 API 设计和错误表现差异大——比如 Linux 下重复 flock 不报错,Windows 下对已锁定文件再次 Lock 会直接抛 IOException

实操建议:

  • 别混用 flock(通过 P/Invoke)和 FileStream.Lock:它们锁的不是同一个内核对象,完全不互斥
  • 统一用 FileStream + FileShare.None + 异常捕获,这是跨平台最稳的 advisory 锁模拟方式
  • Linux 下若用 flock,必须确保所有进程都调用 fcntl(F_SETLK),且不能依赖 close() 自动释放(因为 .NET 的 FileStream 可能延迟 dispose)

FileStream 构造时 FileShare 参数决定锁粒度

很多人以为加了 Lock() 就锁住了,其实真正起作用的是打开文件时的 FileShare。Windows 和 Linux 上,new FileStream(path, FileMode.Open, Fileaccess.Read, FileShare.None) 才能阻止其他进程以读/写方式打开该文件;而 FileShare.ReadFileShare.Write 会让锁形同虚设。

实操建议:

  • 锁文件必须用 FileShare.None,哪怕只是想防止写入——因为 Linux 的 flock 和 Windows 的句柄锁都基于“打开权限”协商
  • 不要在 using 块里只做 Lock() 而不保持流打开:一旦 FileStream 被 dispose,锁立即释放
  • 若需长时间持有锁但又不想阻塞 I/O,可用 FileStream 打开后立刻 Lock(0, long.MaxValue),然后把流存在静态字典里(注意线程安全和泄漏)

跨平台锁失败时的典型错误信息和应对

Linux 下常见 IOException: The process cannot access the file because it is being used by another process(这是 .NET 在 Linux 上对 EBUSY 的翻译,实际和 Windows 错误码一样);Windows 下还可能遇到 Access to the path is denied(权限不足或防病毒软件拦截)。

实操建议:

  • 捕获 IOException,检查 ex.HResult:Windows 上 -2147024864(0x80070020)表示被占用,Linux 上 HResult 通常为 0,得靠 ex.Message.Contains("used by another process")
  • 避免轮询重试时用固定 sleep:改用指数退避(如 10ms → 30ms → 100ms),否则在容器或高并发场景下容易打满 CPU
  • 测试时务必在 WSL、docker(alpine/debian)、Windows Server 上分别验证——尤其是 Docker 默认以 root 运行,而生产环境常以非 root 用户跑,权限差异会导致锁行为突变

为什么不用 MemoryMappedFile 或临时文件做协调

MemoryMappedFile 在 Linux 上需要 /dev/shm 支持,且跨进程命名规则与 Windows 不兼容;临时文件(如 file.lock)看似简单,但存在竞态:两个进程同时检查文件不存在 → 同时创建 → 都认为自己拿到锁。

实操建议:

  • 真要临时文件方案,必须用原子操作:File.Create("file.lock", 0, FileOptions.DeleteOnClose | FileOptions.Asynchronous),并捕获 IOException 判断是否被抢占(Linux/macos 下成功则拿到锁,Windows 下需额外判断 Win32Exception.NativeErrorCode == 5
  • 如果业务允许,优先用外部协调服务(redisSET key val NX PX 30000)代替文件锁——文件锁本质是妥协方案,只适合无外部依赖的嵌入式或单机工具
  • 所有锁逻辑必须带超时:无论是 FileStream 的打开超时,还是业务层的租约续期,否则一个崩溃进程可能永久霸占锁

真正麻烦的不是怎么加锁,而是怎么让所有调用方都遵守同一套打开+检查+释放流程。漏掉任意一环,顾问锁就变成幻觉。

text=ZqhQzanResources