c# - (C#) 使文件读/写线程安全(并考虑性能)

标签 c# thread-safety file-handling

这是我的示例代码,使 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 使用一个锁。我的实现正确吗?

最佳答案

不,有几个原因。

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

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

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

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

这样ReadWriterLock仅在需要时才会创建。

第二点是ReadWriterLock本身,您可能想使用 ReadWriterLockSlim相反,它是一个新的轻量级资源高效版本。你还应该考虑当资源获取超时、抛出异常时怎么办?重试吗?

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

关于c# - (C#) 使文件读/写线程安全(并考虑性能),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36301470/

相关文章:

c# - 为什么调用 Enumerable.First() 似乎返回枚举中第一项的副本

c# - 使用 C# 比较两个 ArrayList 内容

java - 尝试写入文件时发生 Android ENOENT 错误

c - fscanf 无法从 txt 文件读取数据

java - BufferedReader 可以读取字节吗?

c# - 文件异常DRY原理C#

c# - 使用 Jquery 将空字典发布到 ASP.NET MVC4 操作

c - 线程在等待变量更改时陷入无限循环

java - 如何使循环队列完全线程安全

Java线程设计