【问题标题】:(C#) to make file read/write thread-safe (and considering performance)(C#)使文件读/写线程安全(并考虑性能)
【发布时间】:2016-07-18 00:31:45
【问题描述】:

这是我的示例代码,用于使 FileUtil 类成为一个安全的文件 IO 处理程序。

public static class FileUtil {
    private static ConcurrentDictionary<string, ReaderWriterLock> s_locks = new ConcurrentDictionary<string, ReaderWriterLock>();

    public static string ReadFile(string path) {
        var rwLock = s_locks.GetOrAdd(path, new ReaderWriterLock());
        rwLock.AcquireReaderLock(1000);
        string data = File.ReadAllText(path);
        rwLock.ReleaseReaderLock();
        return data;
    }

    public static void WriteFile(string path, string data) {
        var rwLock = s_locks.GetOrAdd(path, new ReaderWriterLock());
        rwLock.AcquireWriterLock(1000);
        using (StreamWriter sw = new StreamWriter(path, true)) {
            sw.Write(data);             
        }
        rwLock.ReleaseWriterLock();
    }
}

如您所见,我创建了一个并发字典,为不同的文件持有不同的锁,以避免所有文件 IO 使用一个锁。我的实现是否正确?

【问题讨论】:

    标签: c# thread-safety file-handling


    【解决方案1】:

    不,有几个原因。

    首先不要忘记文件系统可以为您提供所需的并发性,然后您就不需要专门实现任何东西。请注意,线程安全并不意味着资源不能被并发访问,而是它的使用不会导致失败(在非常广泛的意义上)。

    依赖操作系统进行并发的一种可能实现是:

    public static string ReadFile(string path) {
        for (int retry=0; retry < 3; ++retry) {
            try {
                return File.ReadAllText(path);
            }
            catch (IOException e) {
                // 0x80070020 is value for ERROR_SHARING_VIOLATION
                if (Marshal.GetHRForException(e) == 0x80070020) {
                    Thread.Sleep(1000); // Wait and try again
                    continue;
                }
    
                throw;
            }
        }
    }
    

    WriteFile() 的代码很简单。

    请注意,通过这种方式,您还将处理不同进程之间的共享冲突,并且您将遵守打开文件时声明的共享规则;例如File.WriteAllText() 指定FileShare.Read 允许并发读取但不允许并发写入,请小心,因为读者可能会读取不是最新的内容,如果它不是您想要的,那么您应该删除 File.WriteAllText() 来指定FileShare.NoneFileStream 构造函数中。


    关于一般用法的一些其他说明。

    您正在使用具有立即值的ConcurrentDictionary&lt;TKey, TValue&gt;.GetOrAdd()。这意味着,即使字典已经包含给定路径的ReaderWriterLock,您每次都会构造一个新对象。初始化期间没有获取锁,但它仍然是一个昂贵的操作,那么你应该(在其他代码中!)使用其他重载:

    var rwLock = s_locks.GetOrAdd(path, () => new ReaderWriterLock());
    

    这样ReadWriterLock 将仅在需要时创建。

    第二点是ReadWriterLock 本身,你可能想用ReadWriterLockSlim 代替,它是一个新的轻量级资源高效版本。你也应该考虑一下资源获取超时,抛出异常怎么办?重试?

    最后一点是关于用法的:想一想,在您将该代码用于 1000 个文件之后,您的字典将包含 1000 个(可能未使用的)对象。在另一种情况下,您可以考虑在每次使用后丢弃它们。

    【讨论】:

    • 谢谢阿德里亚诺·雷佩蒂。你说的对。但是对于我的字典锁定示例,它对其他线程敏感的资源读取器/写入器有帮助吗?
    • @ineztia 是的,我添加了一些关于一般用法的注释。
    猜你喜欢
    • 1970-01-01
    • 2012-03-08
    • 1970-01-01
    • 1970-01-01
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    相关资源
    最近更新 更多