4

我试图防止多线程服务器中的数据竞争。我的问题如下:有一个List<RServer>,类型RServer是一个有几个字段的类。现在,服务器有多个线程同时运行,它们可以修改List(添加更多项目)和单个RServer实例(更改字段)。

因此,我的策略是readonly object RServerLock = new object( )在每个RServer实例中创建 a 并附加 areadonly object RServerListLock = new object( )并将所有修改(ListRServer实例)的代码包含在 a 中lock。这安全吗?如果一个线程试图锁定RServerLock 另一个线程正在锁定它会发生什么?

4

6 回答 6

8

如果你有一个竞争锁,第二个线程必须等到第一个线程释放锁。

您的计划听起来几乎没问题 - 但您在读取数据时也需要锁定,以确保您获得最新的值和一致的值。否则,您可能会在一个线程中编写一些值的一半,并在不同的线程中同时看到一些新值(但可能不是全部)和旧值。

如果您可以尽可能避免这样做,您的生活会更轻松 :) 不可变类型使线程更简单。

不要忘记,如果您的代码同时需要两个锁(例如,添加一个 RServer 并以原子方式修改另一个),您必须确保始终以相同的顺序获取锁 - 如果一个线程试图获取在持有锁 A 时锁定 B,并且另一个线程在持有锁 B 时尝试获取锁 A,您将最终陷入死锁。

有关详细信息,请参阅我的线程教程Joe Albahari 的。此外,如果您对并发感兴趣,Joe Duffy 有一本非常好的书即将出版。

于 2008-10-21T22:09:41.850 回答
4

看起来你有一个 ReaderWriterLock 的主要候选人。最好使用的类(如果您的运行时支持它,我认为是 3.0+)是 ReaderWriterLockSlim,因为原始的 ReaderWriterLock 存在性能问题。

一位 MSDN 杂志的作者也遇到了 RWLS 类的问题,我不会在这里详细介绍,但您可以在这里查看

我知道下面的代码会激起 IDisposable 纯粹主义者的愤怒,但有时它确实是很好的语法糖。无论如何,您可能会发现以下内容很有用:

    /// <summary>
    /// Opens the specified reader writer lock in read mode,
    /// specifying whether or not it may be upgraded.
    /// </summary>
    /// <param name="slim"></param>
    /// <param name="upgradeable"></param>
    /// <returns></returns>
    public static IDisposable Read(this ReaderWriterLockSlim slim, bool upgradeable)
    {
        return new ReaderWriterLockSlimController(slim, true, upgradeable);
    } // IDisposable Read

    /// <summary>
    /// Opens the specified reader writer lock in read mode,
    /// and does not allow upgrading.
    /// </summary>
    /// <param name="slim"></param>
    /// <returns></returns>
    public static IDisposable Read(this ReaderWriterLockSlim slim)
    {
        return new ReaderWriterLockSlimController(slim, true, false);
    } // IDisposable Read

    /// <summary>
    /// Opens the specified reader writer lock in write mode.
    /// </summary>
    /// <param name="slim"></param>
    /// <returns></returns>
    public static IDisposable Write(this ReaderWriterLockSlim slim)
    {
        return new ReaderWriterLockSlimController(slim, false, false);
    } // IDisposable Write

    private class ReaderWriterLockSlimController : IDisposable
    {
        #region Fields

        private bool _closed = false;
        private bool _read = false;
        private ReaderWriterLockSlim _slim;
        private bool _upgrade = false;

        #endregion Fields

        #region Constructors

        public ReaderWriterLockSlimController(ReaderWriterLockSlim slim, bool read, bool upgrade)
        {
            _slim = slim;
            _read = read;
            _upgrade = upgrade;

            if (_read)
            {
                if (upgrade)
                {
                    _slim.EnterUpgradeableReadLock();
                }
                else
                {
                    _slim.EnterReadLock();
                }
            }
            else
            {
                _slim.EnterWriteLock();
            }
        } //  ReaderWriterLockSlimController

        ~ReaderWriterLockSlimController()
        {
            Dispose();
        } //  ~ReaderWriterLockSlimController

        #endregion Constructors

        #region Methods

        public void Dispose()
        {
            if (_closed)
                return;
            _closed = true;

            if (_read)
            {
                if (_upgrade)
                {
                    _slim.ExitUpgradeableReadLock();
                }
                else
                {
                    _slim.ExitReadLock();
                }
            }
            else
            {
                _slim.ExitWriteLock();
            }

            GC.SuppressFinalize(this);
        } // void Dispose

        #endregion Methods
    } // Class ReaderWriterLockSlimController

将其放入扩展方法类(公共静态类 [Name])中并按如下方式使用它:

using(myReaderWriterLockSlim.Read())
{
  // Do read operations.
}

或者

using(myReaderWriterLockSlim.Read(true))
{
  // Read a flag.
  if(flag)
  {
    using(myReaderWriterLockSlim.Write()) // Because we said Read(true).
    {
      // Do read/write operations.
    }
  }
}

或者

using(myReaderWriterLockSlim.Write()) // This means you can also safely read.
{
  // Do read/write operations.
}
于 2008-10-22T05:21:36.270 回答
2

如果一个线程试图锁定一个已经被锁定的对象,它将阻塞直到锁定被释放。当两个线程试图同时锁定它时不会出现并发问题,因为锁定是原子操作,因此其中一个线程将始终成为锁定的受害者并最终阻塞。

如果您需要对 RServer 实例进行完全锁定,那么您的策略听起来很合理。如果您可以专门锁定特定的 RServer 实例字段,那可能会更有效。然而,它会增加锁定操作的数量并且会更加复杂。

于 2008-10-21T22:09:32.380 回答
0

那是安全的。如果一个线程获得了锁,其他线程将不得不等待直到锁被释放。

但是,尽管不太可能,但您可能会遇到性能问题,因为锁可能过于全局。这实际上取决于您的状态是什么以及这些线程如何改变它,所以我无法帮助您。

于 2008-10-21T22:08:50.403 回答
0

重申一下 - 每个 RServer 实例都有一个名为 RServerLock 的变量,该变量将使用锁定块锁定。

线程 1 (T1) 锁定 RServer 1 (R1)。线程 2 (T2) 尝试修改 R1,这会导致进入 R1 锁定块。在这种情况下,T2 将等到 T1 完成。

需要真正小心的一件事是最终有多少 RServer 实例。如果您最终得到过多的实例,那么您将携带额外的 20 字节数据只是为了锁定。此时,您可能需要考虑锁定条带化。

于 2008-10-21T22:08:51.167 回答
0

如果我正确理解您的问题,听起来您正在尝试创建一个已经存在完美轮子的轮子。

查看 MSDN 中的 system.threading 命名空间。它有许多专门为处理此类情况而设计的锁定机制。

http://msdn.microsoft.com/en-us/library/system.threading.aspx

于 2008-10-21T22:12:00.113 回答