我在C#中有一个情况,我有一个简单类型的列表。可以通过多个线程访问此列表:可以添加或删除条目,并且可以检查条目是否存在。我已经将列表封装在一个对象中,这个对象到目前为止只暴露了这三个操作。
我有几个案件要处理(与我刚才提到的方法不完全相同)。
整个想法是条目的存在表示锁定。如果存在条目,则无法更改其标识的对象,并且代码无法继续,因为它正在其他位置进行修改。
这些似乎是简单的新手情况,但我在并发问题上让自己感到高兴,这让我有点偏执,而且我也不熟悉C#的并发机制。
处理此问题的最佳方法是什么?我完全没了?应该检查并添加(测试和设置?)组合成第四个原子操作?我只是将锁块添加到访问列表的方法中吗?
另外,是否有可能对这种事情进行单元测试(不是简单的操作,并发情况)?
答案 0 :(得分:8)
单元测试肯定很难。
这一切都可以通过.NET中的“本机”并发机制合理地完成:锁定语句和Monitor.Wait
/ Monitor.PulseAll
。除非你的每个项目都有一个单独的监视器,否则你需要在删除任何东西时唤醒所有线程 - 否则你将无法告诉“正确”的线程被唤醒。
如果它只是一个 set 项目,那么您可能希望使用HashSet<T>
而不是List<T>
代表该集合,顺便说一下 - 你没有提到的是订购。
示例代码,假设一个集合适合您:
using System;
using System.Collections.Generic;
using System.Threading;
public class LockCollection<T>
{
private readonly HashSet<T> items = new HashSet<T>();
private readonly object padlock = new object();
public bool Contains(T item)
{
lock (padlock)
{
return items.Contains(item);
}
}
public bool Add(T item)
{
lock (padlock)
{
// HashSet<T>.Add does what you want already :)
// Note that it will return true if the item
// *was* added (i.e. !Contains(item))
return items.Add(item);
}
}
public void WaitForNonExistence(T item)
{
lock (padlock)
{
while (items.Contains(item))
{
Monitor.Wait(padlock);
}
}
}
public void WaitForAndAdd(T item)
{
lock (padlock)
{
WaitForNonExistence(item);
items.Add(item);
}
}
public void Remove(T item)
{
lock (padlock)
{
if (items.Remove(item))
{
Monitor.PulseAll(padlock);
}
}
}
}
(完全未经测试,诚然。您可能还想为等待代码指定超时......)
答案 1 :(得分:8)
虽然#1可能是最简单的,但它本质上是一种无用的方法。除非在完成对“条目存在”的查询后持有相同的锁,否则实际上返回“在过去的某个时刻存在条目”。它没有提供有关当前条目存在的任何信息。
在发现列表中的值然后执行任何检索操作之间,删除该值,另一个线程可能会为您删除它。
包含并发列表上的操作应该与您计划在该检查的真/假存在的情况下执行的操作相结合。例如,TestAdd()或TestRemove()
答案 2 :(得分:2)
这是一个适当的,并发的,线程安全的,可并行化的并发列表实现 http://www.deanchalk.me.uk/post/Task-Parallel-Concurrent-List-Implementation.aspx
答案 3 :(得分:1)
有一种产品可用于在单元测试中查找竞争条件等。它被称为TypeMock Racer。但是,我无法赞同或反对其有效性。 :)