我有一个名为
ClientManager
的并发类,它使用哈希集和 ReadWriterLock
通过他们的 Guid
来注册我的“客户”:
internal class ClientManager : IClientManager
{
private readonly HashSet<Guid> _clients = new HashSet<Guid>();
private readonly ReaderWriterLock _lock = new ReaderWriterLock();
public IEnumerable<Guid> Clients
{
get
{
try
{
_lock.AcquireReaderLock(100.Milliseconds());
var result = _clients.ToList();
return result;
}
finally
{
_lock.ReleaseReaderLock();
}
}
}
public void AddClient(Guid clientId, string login, string appName)
{
try
{
_lock.AcquireWriterLock(100.Milliseconds());
_clients.Add(clientId);
}
finally
{
_lock.ReleaseWriterLock();
}
}
public void RemoveClient(Guid clientId)
{
try
{
_lock.AcquireWriterLock(100.Milliseconds());
_clients.Remove(clientId);
}
finally
{
_lock.ReleaseWriterLock();
}
}
}
我创建了一个单元测试,它使用三个任务来访问哈希集/添加客户端/删除客户端。在单元测试结束时,我希望哈希集中剩下 0 个条目:
[Fact]
public void ClientManager_Should_Handle_Concurent_Operations()
{
var clientManager = new ClientManager();
var isDoneAdding = false;
var addTask = new Task(() =>
{
for (var i = 0; i < 10000; i++)
{
//Adding client should enter write lock
clientManager.AddClient(Guid.NewGuid(), $"login_{i}", $"appName_{i}");
}
isDoneAdding = true;
});
var accessTask = new Task(() =>
{
for (var i = 0; i < 10000; i++)
{
//Getting clients should enter read lock
var clients = clientManager.Clients;
}
});
var removeTask = new Task(() =>
{
for (var i = 0; i < 10000; i++)
{
var existingGuid = clientManager.Clients.FirstOrDefault();
if (existingGuid != default)
{
//Removing client should enter write lock
clientManager.RemoveClient(existingGuid);
}
}
//refactor that
while (clientManager.Clients.Count() > 0 && isDoneAdding)
{
var existingGuid = clientManager.Clients.FirstOrDefault();
if (existingGuid != default)
{
clientManager.RemoveClient(existingGuid);
}
}
});
accessTask.Start();
addTask.Start();
removeTask.Start();
Task.WaitAll(accessTask, addTask, removeTask);
Assert.True(clientManager.Clients.Count() == 0);
}
单元测试总是失败: 测试失败
我更改了
ConcurrentDictionary
的哈希集和锁定系统,如下所示:
internal class ClientManager : IClientManager
{
//We don't need the value here so just put a byte to reduce memory footprint
private readonly ConcurrentDictionary<Guid, byte> _clients = new ConcurrentDictionary<Guid, byte>();
private readonly ReaderWriterLock _lock = new ReaderWriterLock();
public IEnumerable<Guid> Clients
{
get
{
return _clients.Keys;
}
}
public void AddClient(Guid clientId, string login, string appName)
{
_clients.TryAdd(clientId, default);
}
public void RemoveClient(Guid clientId)
{
_clients.Remove(clientId, out _);
}
}
单元测试连续成功320次:测试成功
我不明白哈希集锁定系统缺少什么才能正常工作,有人可以帮助我了解发生了什么吗?
存在很多问题,但此特定测试未通过的根本原因是您标记为重构的代码:
while (clientManager.Clients.Count() > 0 && isDoneAdding) {
var existingGuid = clientManager.Clients.FirstOrDefault();
if (existingGuid != default) {
clientManager.RemoveClient(existingGuid);
}
}
需要写成这样:
//still refactor that
while (!isDoneAdding) ; // spinwait for the adding to end
while (clientManager.Clients.Count() > 0 ) {
var existingGuid = clientManager.Clients.FirstOrDefault();
if (existingGuid != default) {
clientManager.RemoveClient(existingGuid);
}
}
其他问题:
ReaderWriterLock
已弃用,取而代之的是 ReaderWriterLockSlim
IEnumerable<Guid> Clients
中返回一个副本 (ToList),该副本很快就会变得陈旧。
if (existingGuid != default) {
导致其 10_000 次迭代比添加任务更快完成。因此,当删除任务检查 isDoneAdding
时,它仍然为 false,因此不会进入循环来删除所有尚未删除的项目。从逻辑上讲,这给了我们最终的 Count
> 0。如果不是出于教育目的,我会研究在某个地方找到经过实战测试的 ConcurrentHashSet 实现。也许是
建议的
György Kőszeg 在评论中:
ThreadSafeHashSet<T>