我们团队中就锁定声明进行了辩论 - 我希望得到一些有关此问题的外部反馈。
有问题的代码部分是:
lock (IndexLock)
{
currentIndex = Index;
}
您可以在下面看到部分课程:
public class BuilderService
{
private ImmutableList<Model> Index { get; set; } = ImmutableList<Model>.Empty;
private object IndexLock { get; } = new();
public Add(Model item)
{
lock (IndexLock)
{
Index = Index.Add(item);
}
}
public IEnumerable<Model> GetFilterLinesExcluding()
{
var filters = new List<Model>();
ImmutableList<FilterModel> currentIndex;
lock (IndexLock)
{
currentIndex = Index;
}
foreach (var filter in currentIndex)
{
...
}
}
}
无需详细讨论,争论围绕以下现象:
在
lock
周围有 currentIndex = Index
有意义吗?
主题:
GetFilterLinesExcluding
多线程访问的方法。Add
方法可以随时调用。如果需要,我会提供更多信息。
提前谢谢您!
在
周围有lock
有意义吗?currentIndex = Index
lock
语句是强制线程安全的最简单方法,无需进行大量研究。 lock
确保一次只有一个线程可以与共享状态交互,如果您小心且遵守纪律,则使多线程相对容易。当执行流进入和退出受保护的部分时,lock
语句还隐式添加适当的内存屏障,因此您不必考虑它们。
我们团队就
声明进行了辩论。lock
我猜您团队中的某人建议删除
lock
线周围的 currentIndex = Index;
。他们的论点很可能是 ImmutableList<T>
是不可变的,因此在枚举 ImmutableList<T>
时另一个线程不可能改变它。这是一个理性的论点,但无锁多线程却一点也不理性。您可能不知道,C# 编译器以及 .NET Jitter 和 CPU 处理器都可以对程序指令进行重新排序,前提是重新排序不会影响单个线程上的执行。换句话说,编译器/抖动/处理器假定您的程序是单线程的,除非您通过隐式或显式添加内存屏障来告诉它。因此,如果省略 lock
周围的 currentIndex = Index;
,则该行之后的指令可能会在此行之前执行。在这种特殊情况下,我看不到该行之后可以移动并放置在该行之前的任何内容,从而导致问题。因此,从技术上讲,当使用任何符合标准的 C# 编译器进行编译并在任何符合标准的 CPU 架构上运行时,省略 lock
可能不会改变程序的行为,但我不会把钱押在它上面。在走这条路之前,您需要征求内存模型和 CPU 架构方面真正“专家”的人的意见,而我不是那么专家。
如果您询问了 lock
线周围的
Index = Index.Add(item);
,我当然可以看到移除 that锁的问题。然后线程就可以将未初始化的
ImmutableList<T>
对象分配给 Index
字段,然后对其进行初始化。从单线程程序的角度来看,这没有任何区别,因此这种重新排序是允许的。当然,其他线程可能会尝试枚举部分初始化的 ImmutableList<T>
实例,从而允许发生各种混乱。顺便说一句,锁定的一种更高效且争议更少的替代方案是使用 关键字:
private volatile ImmutableList<Model> _index;
// Not thread-safe. Only one thread is supposed to call it.
public Add(Model item)
{
_index = _index.Add(item);
}
// Thread-safe. Multiple threads can call it.
public IEnumerable<Model> GetFilterLinesExcluding()
{
List<Model> filters = new();
foreach (var filter in _index)
{
...
}
}
每当读取或更新
volatile
时,
_index
都会添加适当的屏障,从而防止在部分初始化状态下查看 ImmutableList<Model>
的情况。如果您也想使 Add
线程安全,而不使用
lock
语句,则可以使用 ImmutableInterlocked.Update
API。这将防止可能导致某些
Model
物品丢失的竞争条件。