如何制作一个只允许一个线程从资源中读取的锁

本文关键字:资源 线程 读取 许一个 何制作 一个 | 更新日期: 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会出现缺口,但有一些方法可以弥补这一点。