如何锁定文件并避免在写入时读取
Posted
技术标签:
【中文标题】如何锁定文件并避免在写入时读取【英文标题】:How to Lock a file and avoid readings while it's writing 【发布时间】:2011-02-05 17:26:50 【问题描述】:我的 Web 应用程序从文件系统返回一个文件。这些文件是动态的,所以我无法知道它们的名称或数量。当此文件不存在时,应用程序会从数据库中创建它。我想避免两个不同的线程同时重新创建同一个文件,或者一个线程尝试返回该文件而另一个线程正在创建它。
另外,我不想锁定所有文件通用的元素。因此,我应该在创建文件时锁定它。
所以我想锁定一个文件直到它的重新创建完成,如果其他线程试图访问它......它将不得不等待文件被解锁。
我一直在阅读有关 FileStream.Lock 的信息,但我必须知道文件长度,并且它不会阻止其他线程尝试读取文件,因此它不适用于我的特定情况。
我也一直在阅读有关 FileShare.None 的内容,但是如果其他线程/进程尝试访问该文件,它将引发异常(哪种异常类型?)...所以我应该开发一个“出现故障时再试一次” " 因为我想避免产生异常......而且我不太喜欢这种方法,尽管也许没有更好的方法。
使用 FileShare.None 的方法或多或少是这样的:
static void Main(string[] args)
new Thread(new ThreadStart(WriteFile)).Start();
Thread.Sleep(1000);
new Thread(new ThreadStart(ReadFile)).Start();
Console.ReadKey(true);
static void WriteFile()
using (FileStream fs = new FileStream("lala.txt", FileMode.Create, FileAccess.Write, FileShare.None))
using (StreamWriter sw = new StreamWriter(fs))
Thread.Sleep(3000);
sw.WriteLine("trolololoooooooooo lolololo");
static void ReadFile()
Boolean readed = false;
Int32 maxTries = 5;
while (!readed && maxTries > 0)
try
Console.WriteLine("Reading...");
using (FileStream fs = new FileStream("lala.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
using (StreamReader sr = new StreamReader(fs))
while (!sr.EndOfStream)
Console.WriteLine(sr.ReadToEnd());
readed = true;
Console.WriteLine("Readed");
catch (IOException)
Console.WriteLine("Fail: " + maxTries.ToString());
maxTries--;
Thread.Sleep(1000);
但我不喜欢我必须捕获异常,尝试多次并等待不准确的时间:|
【问题讨论】:
它是 FileShare.None 而不是 FileAccess.None(FileAccess 定义了您的应用程序具有的访问权限,而 FileShare 用于根据需要锁定文件) 关于其他话题,记得在你的应用程序被销毁时解锁文件,我讨厌锁定之后仍然存在。 我已经编辑并修复了它,谢谢! 【参考方案1】:您可以使用流构造函数的 FileMode.CreateNew 参数来处理这个问题。其中一个线程将丢失并发现该文件已由另一个线程早一微秒创建。并且会得到一个 IOException。
然后它需要旋转,等待文件完全创建。您使用 FileShare.None 强制执行。在这里捕捉异常并不重要,它无论如何都在旋转。除非您 P/Invoke,否则没有其他解决方法。
【讨论】:
看来你是对的,没有其他解决方法。谢谢!【参考方案2】:我认为正确的方法如下: 创建一组字符串,你将保存当前文件名 所以一个线程会一次处理文件,像这样
//somewhere on your code or put on a singleton
static System.Collections.Generic.HashSet<String> filesAlreadyProcessed= new System.Collections.Generic.HashSet<String>();
//thread main method code
bool filealreadyprocessed = false
lock(filesAlreadyProcessed)
if(set.Contains(filename))
filealreadyprocessed= true;
else
set.Add(filename)
if(!filealreadyprocessed)
//ProcessFile
【讨论】:
这就是问题所在,我不想为所有文件锁定一个公共元素。首先,获取锁很昂贵,而且我不想为每次请求文件的调用获取锁,无论文件是否已经存在。其次,我不想阻止试图获取不同文件的线程,因为我正在创建其中一个。由于这些原因,我想锁定文件本身。干杯。 您是否测量过获取锁和阻塞直到完成的时间与线程唤醒、检查访问、获取异常、休眠和重复几次的时间?我希望锁定策略在这里会更可取。Thread.Sleep
不太希望阻塞锁。如果写线程提前结束怎么办?读取线程不会唤醒。您可能需要考虑使用ManualResetEvent
来控制两个线程之间的访问。【参考方案3】:
您有办法识别正在创建的文件吗?
假设这些文件中的每一个都对应于数据库中的唯一 ID。您创建一个集中位置(Singleton?),这些 ID 可以与可锁定的东西(字典)相关联。需要读取/写入其中一个文件的线程执行以下操作:
//Request access
ReaderWriterLockSlim fileLock = null;
bool needCreate = false;
lock(Coordination.Instance)
if(Coordination.Instance.ContainsKey(theId))
fileLock = Coordination.Instance[theId];
else if(!fileExists(theId)) //check if the file exists at this moment
Coordination.Instance[theId] = fileLock = new ReaderWriterLockSlim();
fileLock.EnterWriteLock(); //give no other thread the chance to get into write mode
needCreate = true;
else
//The file exists, and whoever created it, is done with writing. No need to synchronize in this case.
if(needCreate)
createFile(theId); //Writes the file from the database
lock(Coordination.Instance)
Coordination.Instance.Remove[theId];
fileLock.ExitWriteLock();
fileLock = null;
if(fileLock != null)
fileLock.EnterReadLock();
//read your data from the file
if(fileLock != null)
fileLock.ExitReadLock();
当然,不遵循这个确切锁定协议的线程将可以访问该文件。
现在,锁定 Singleton 对象肯定不是理想的,但如果您的应用程序需要全局同步,那么这是实现它的一种方法。
【讨论】:
与@hworangdo 代码相同的问题,在每个请求中您都必须获得一个锁,即使您不需要它。 @vtortola:是的。为我的回答辩护:获取锁并不昂贵,(衡量一下,它真的没什么,特别是与文件 IO 相比)但等待另一个线程释放锁是。您可以尝试找到一个无锁字典实现。您只需要在需要创建文件的情况下小心,以便只有一个线程负责创建它。 你可能是对的,我从来没有测试过自己购买锁有多昂贵,我知道是因为我读过它。等待另一个线程释放锁肯定会更昂贵,但每个文件只会发生一次。稍后我会测试您的方法,也许您的方法更快。谢谢!【参考方案4】:你的问题让我深思。
如果不是让每个线程负责文件访问并让它们阻塞,如果您使用需要持久化的文件队列并有一个后台工作线程出队和持久化怎么办?
当后台工作人员启动时,您可以让 Web 应用程序线程返回 db 值,直到文件确实存在。
我已经发布了一个非常简单的example of this on GitHub。
随意试一试,让我知道你的想法。
仅供参考,如果你没有 git,你可以使用 svn 拉取它http://svn.github.com/statianzo/MultiThreadFileAccessWebApp
【讨论】:
【参考方案5】:问题是旧的,并且已经有一个标记的答案。不过,我想发布一个更简单的替代方案。
我觉得我们可以直接在文件名上使用lock语句,如下:
lock(string.Intern("FileLock:absoluteFilePath.txt"))
// your code here
一般来说,锁定一个字符串是个坏主意,因为有 String Interning。但在这种特殊情况下,它应该确保没有其他人能够访问该锁。只需在尝试读取之前使用相同的锁定字符串。在这里实习对我们有用,而不是反对。
PS:文本 'FileLock' 只是一些任意文本,以确保其他字符串文件路径不受影响。
【讨论】:
【参考方案6】:你为什么不直接使用数据库 - 例如。如果您有办法将文件名与其包含的数据库中的数据相关联,只需向数据库添加一些信息,指定文件当前是否存在该信息以及创建时间、文件中的信息有多陈旧等. 当一个线程需要一些信息时,它会检查数据库以查看该文件是否存在,如果不存在,它会在表中写出一行,说明它正在创建文件。完成后,它会使用布尔值更新该行,表示该文件已准备好供其他人使用。
这种方法的好处 - 你的所有信息都在一个地方 - 所以你可以很好地恢复错误 - 例如。如果创建文件的线程由于某种原因严重死亡,另一个线程可能会出现并决定重写文件,因为创建时间太旧了。您还可以创建简单的批处理清理过程并获得有关某些数据用于文件的频率、信息更新频率(通过查看创建时间等)的准确数据。此外,由于不同的线程在各处寻找不同的文件,因此您不必在文件系统中进行多次磁盘寻道,尤其是当您决定让多台前端机器在一个公共磁盘上寻道时。
棘手的事情 - 您必须确保您的数据库支持线程在创建文件时写入的表上的行级锁定,否则表本身可能会被锁定,这可能会导致速度慢得无法接受。
【讨论】:
以上是关于如何锁定文件并避免在写入时读取的主要内容,如果未能解决你的问题,请参考以下文章
如果我有一个线程写入和多个读取,我如何只在写入时锁定而不是读取?