如何制作一个只允许一个线程从资源中读取的锁
本文关键字:资源 线程 读取 许一个 何制作 一个 | 更新日期: 2023-09-27 18:00:11
我有一个文件,其中包含一个整数ID值。当前读取文件受到ReaderWriterLockSlim
的保护,如下所示:
public int GetId()
{
_fileLock.EnterUpgradeableReadLock();
int id = 0;
try {
if(!File.Exists(_filePath))
CreateIdentityFile();
FileStream readStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
StreamReader sr = new StreamReader(readStream);
string line = sr.ReadLine();
sr.Close();
readStream.Close();
id = int.Parse(line);
return int.Parse(line);
}
finally {
SaveNextId(id); // increment the id
_fileLock.ExitUpgradeableReadLock();
}
}
问题是GetId()
之后的后续操作可能会失败。正如您所看到的,GetId()
方法每次都会增加ID,而不管它发出ID后会发生什么。发出的ID可能会挂起(如上所述,可能会发生异常)。随着ID的增加,一些ID可能会被闲置。
所以我想把SaveNextId(id)
移出来,去掉它(SaveNextId()
实际上也使用了锁,只是它是EnterWriteLock
)。并在执行完所有必需的方法后从外部手动调用它。这带来了另一个问题——多个线程可能在执行SaveNextId()
之前进入GetId()
方法,并且它们可能都收到相同的ID。
我不希望有任何解决方案,在手术后我必须更改ID,以任何方式更正它们,因为这不好,可能会导致更多问题。
我需要一个解决方案,在这个解决方案中,我可以以某种方式回调到FileIdentityManager(即处理这些ID的类),并让管理器知道它可以执行下一个ID的保存,然后释放包含ID的文件的读锁。
重要的是,我想复制关系数据库的自动增量行为——如果在行插入过程中出现任何问题,ID将不会被使用,它仍然可以使用,但也永远不会发出相同的ID。希望这个问题可以理解,以便您提供一些解决方案。。
更新:有关我想要的行为的更多详细信息,请参阅答案的评论
重要的是,我想复制关系数据库自动增量behavior-如果出现任何问题在行插入过程中,ID不是已使用,但仍可使用但也从来没有发生过发出相同的ID。希望这个问题对于您可以提供一些解决方案。
一般来说,这不是我观察到的行为。当您在事务中插入具有自动增量的行并将其回滚时,您就丢失了ID。
所以在我看来,你实现这一点的方式是正确的。
更新确保"不想将它们浪费在不成功的文件保存、不成功的类型转换等方面"的唯一方法是,从您请求新ID的那一刻起,将阻止代码的范围更改为阻止,直到您的保存完成,并且在未能将增量回滚到ID时。
这将大大降低您可以实现的并行度。
如果您想保持更高的并行性,在请求ID之前,您应该检查所有可能的内容,例如检查类型和格式错误。
显然,有些事情,比如外部错误(IO异常),你根本无法处理。
private static readonly object _lock = new object();
public int GetId()
{
lock(_lock)
{
//You code to get ID here
}
}
您在数据库中看到的行为是可能的,因为ID生成和行插入是原子的。如果你想在你的应用程序中有这种行为,那么我建议你只在存储数据之前立即获取ID。这将把您的"事务范围"减少到尽可能小的窗口,并应防止任何异常干扰。
如果出于某种不好的原因,这是不可能的,一个替代方案可能是有一个"ID代理"来缓存ID计数器。它将从文件中读取当前计数器,将其递增一些数字(比如100),然后通过单线程方法将连续的ID分发给所有调用方。当它分发完所有100份后,它会再次更新文件。在关闭时,它会使用它发出的最后一个值最后一次写入文件。唯一的问题是,如果你的系统崩溃,你的ID会出现缺口,但有一些方法可以弥补这一点。