C# 线程安全工厂
我有一个工厂,它的工作是在需要时在每次调用时创建新实例。我的代码如下所示:C# 线程安全工厂,c#,thread-safety,locking,factory,C#,Thread Safety,Locking,Factory,我有一个工厂,它的工作是在需要时在每次调用时创建新实例。我的代码如下所示: public class Factory { public object Get() => new object(); } 我尝试使用以下nunit测试来测试多线程部分: public async Task Get_ThreadSaftyTests() { const int limit = 10_000 var concurrentCollection = new Con
public class Factory
{
public object Get()
=> new object();
}
我尝试使用以下nunit测试来测试多线程部分:
public async Task Get_ThreadSaftyTests()
{
const int limit = 10_000
var concurrentCollection = new ConcurrentBag<object>();
var instance = new Factory();
var tasks = new List<Task>(limit);
for (int i = 0; i < limit; i++)
{
tasks.Add(Task.Factory.StartNew(() =>
{
concurrentCollection.Add(instance.Get());
}));
}
await Task.WhenAll(tasks);
var hashSet = new HashSet<long>();
foreach (var item in concurrentCollection)
{
hashSet.Add(item.GetHashCode());
}
Assert.AreEqual(limit, hashSet.Count);
}
public异步任务Get\u ThreadSaftyTests()
{
常数整数极限=10_000
var concurrentCollection=new ConcurrentBag();
var实例=新工厂();
var任务=新列表(限制);
对于(int i=0;i
{
concurrentCollection.Add(instance.Get());
}));
}
等待任务。何时(任务);
var hashSet=新hashSet();
foreach(concurrentCollection中的var项)
{
Add(item.GetHashCode());
}
AreEqual(limit,hashSet.Count);
}
这项测试大部分时间都通过了。准确地说是4/5。这有点奇怪
在返回实例之前,我已尝试实现锁定,如下所示:
public class Factory
{
private static readonly Lazy<object> lockObject = new Lazy<object>(true);
public object Get()
{
lock (lockObject.Value)
{
return new object();
}
}
}
公共类工厂
{
private static readonly Lazy lockObject=new Lazy(true);
公共对象Get()
{
锁(lockObject.Value)
{
返回新对象();
}
}
}
但有了这种实现,测试几乎永远不会通过。1/5运行测试通过。这里的问题是我做错了什么
------------------------------更新-------------------------------
如果循环有100000次迭代,那么在这两种情况下,每次测试都会失败
lock (lockObject.Value)
{
return new object();
}
如果循环有6500次迭代,则在这两种情况下它每次都会通过
lock (lockObject.Value)
{
return new object();
}
锁在这种情况下不起作用。调用构造函数可能是线程安全的,也可能不是线程安全的,但是框架中没有任何东西使创建对象成为非线程安全的。也就是说,您需要自己编写一个非线程安全的构造函数,然后由您来正确使用它
然而:
var hashSet = new HashSet<long>();
foreach (var item in concurrentCollection)
{
hashSet.Add(item.GetHashCode());
}
var hashSet=new hashSet();
foreach(concurrentCollection中的var项)
{
Add(item.GetHashCode());
}
GetHashCode
不能保证返回唯一的数字,因为引用可能是64位的,而GetHashCode
返回32位的数字
解决方法应该很简单。更改为
HashSet
并删除.GetHashCode()
这是很多任务,特别是在使用StartNew
时,需要创建这么多任务是非常不寻常的。通常情况下,您只会创建尽可能多的单个处理器。测试完全没有意义。它没有任何用处。主要问题是,您将获得100000个对象,然后查看是否存在任何重复的哈希代码。这是——嗯,这是毫无意义的。无法保证100000个对象中每个对象的哈希代码都是唯一的。不过,这与线程安全无关——这只是一个非常奇怪的测试。我怀疑您应该测试的不是HashSet
,而是HashSet
。然后,您将意识到对象都是唯一的。作为旁注,一般来说,Task.Run
到Task.Factory.StartNew
。另外,ConcurrentBag
是一个集合,几乎没有实际用途,您的示例不是其中之一。ConcurrentQueue
更可取,因为它保留了插入项的顺序,但更好的做法是根本不使用并发集合,使用列表
而不是列表
,并且var objects=wait Task.whalll(tasks)
。