题
我正在尝试阻止多线程服务器中的数据争用。我的问题如下:有一个List<RServer>
,类型RServer
是一个包含多个字段的类。现在,服务器有几个线程全部同时运行,他们可以修改List
(添加更多项)和单个readonly object RServerLock = new object( )
实例(更改字段)。
所以我的策略是在每个readonly object RServerListLock = new object( )
实例中创建一个lock
,另外一个RServerLock
并将所有修改的代码(<=>或<=>实例)包含在< =>。这样安全吗?如果某个线程试图锁定<=> 而另一个锁定它时会发生什么?
解决方案
如果你有一个竞争锁,第二个线程必须等到第一个发布锁。
您的计划听起来几乎没问题 - 但您还需要在读取数据时锁定,以确保获得最新值和一致值。否则你可能会在一个线程中编写一些值,并在一个不同的线程中同时看到一些新值 - 但可能不是全部 - 以及旧值。
如果你能尽可能避免这样做,你的生活会更容易:)不可变的类型使线程变得更加简单。
不要忘记,如果您的代码需要同时需要两个锁(例如,添加一个RServer并以原子方式修改另一个),必须确保始终获取锁定相同的顺序 - 如果一个线程在持有锁A时尝试获取锁B,而另一个线程在持有锁B时尝试获取锁A,则最终会出现死锁。
请参阅我的线程教程或 Joe Albahari的了解更多细节。此外,如果您对并发感兴趣,Joe Duffy有一个优秀书籍很快就会出来。
其他提示
看起来你有一个ReaderWriterLock的主要候选人。最好使用的类(如果运行时支持它,我认为3.0+)是ReaderWriterLockSlim,因为原始的ReaderWriterLock存在性能问题。
其中一位MSDN杂志的作者也遇到了RWLS课程的问题,我不会在这里详细说明,但你可以看看它 here 。
我知道下面的代码会产生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
将它放在扩展方法类(public static class [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.
}
如果线程试图锁定已经锁定的对象,它将一直阻塞,直到锁定被释放。当两个线程试图同时锁定它时,并不存在并发问题,因为锁是一个原子操作,因此其中一个线程将永远是锁的受害者并最终阻塞。
如果您需要完整锁定RServer实例,那么您的策略听起来很合理。如果您可以专门锁定特定的RServer实例字段,那可能会更有效。但是它会增加锁定操作的次数并且会更复杂。
这很安全。如果一个线程获得了锁定,则其他线程必须等到锁定被释放。
然而,由于锁定可能是全局性的,因此不太可能出现性能问题。这实际上取决于你的状态以及它们如何被这些线程变异,所以我无法帮助你。
重述一点 - 每个RServer实例都有一个名为RServerLock的变量,它将使用锁定块锁定。
线程1(T1)锁定RServer 1(R1)。线程2(T2)尝试修改R1,导致输入R1锁定块。在这种情况下,T2将等到T1结束。
要特别注意的一件事是你最终会有多少个RServer实例。如果你最终得到了大量的实例,那么你只需携带20个字节的数据来锁定。此时,您可能需要考虑锁定条带化。
如果我正确地理解你的问题,听起来你正试图创造一个已经存在完美好车轮的车轮。
查看MSDN中的system.threading命名空间。它有许多专门用于处理这种情况的锁定机制。
http://msdn.microsoft.com/en-us /library/system.threading.aspx