如何避免在 ConcurrentBag 上争用<T>。GetEnumerator()



我试图解决一个非常高使用Log Writer类上处理争用的完美问题。当我们从多个线程迭代袋子项目时,我看到的是由ConcurrentBag.GetEnumerator()方法引起的争用。查看GetEnumerator()的代码,我看到该方法调用了FreezeBag(),而FreezeBag()又调用了aquirealalllocks(),这是争用的根源。我正在寻求帮助设计一种方法来避免这种情况。

下面是简短的类设计。该类注册任意数量的日志'IPlugin'对象,我们将其添加到ConcurrentBag实例中。WriteLog()方法在高规模上被许多线程调用。WriteLog()方法必须遍历插件列表,对每个项目调用IPlugin.WriteLog()。

public static class Monitor
{
internal static ConcurrentBag<IPlugin> Plugins = new ConcurrentBag<IPlugin>();
public static void WriteLog(int code, params object[] arguments)
{
foreach (IPlugin plugin in Plugins)
{
plugin.WriteLog(code, arguments);
}
}
public static void RegisterForEvents(IPlugin plugin)
{
if (plugin == null)
{
throw new ArgumentNullException("plugin");
}
Plugins.Add(plugin);
}
}

我的第一个想法是用List代替ConcurrentBag。我可以锁定List.Add()方法以使该线程安全,但随后我需要锁定迭代器周围的WriteLog()内部,因为我们不能在使用迭代器时更改集合。我可以在Register调用期间锁定/争用,因为我们只注册几个IPlugins。我不能承受WriteLog()方法中的任何争用。

有人能帮我解决这个问题吗?

提前感谢!

使用不可变集合找到答案。System.Collections.Immutable

基本上,集合中的项是不可变的,因此不需要锁定GetEnumerator()调用。当我们需要向集合中添加新插件时,将immutableelist复制到新的immutableelist中,并将其赋值给共享静态变量。如果线程已经使用GetEnumerator()获得了先前的枚举,那么它将拥有"旧列表"。这样,大规模的WriteEvent就不会阻塞。


public static class Monitor
{
internal static ImmutableList<IPlugin> Plugins = ImmutableList.Create<IPlugin>();
public static void WriteLog(int code, params object[] arguments)
{
foreach (IPlugin plugin in Plugins)
{
plugin.WriteLog(code, arguments);
}
}
public static void RegisterForEvents(IPlugin plugin)
{
if (plugin == null)
{
throw new ArgumentNullException("plugin");
}
Plugins = Plugins.Add(plugin);
}
}

最新更新