如果您正在设计需要保证线程安全的数据结构和类型,那么请确保将锁和其他构造放入这些类型中以维护这些保证。
然而,仅有锁是不够的。
拿一本简单的字典。假设你想确保字典内部的数据结构不会被多个线程破坏,所以你引入了锁。但是,如果外部代码这样做:
if (!dict.ContainsKey(key))
dict.Add(key, value);
那么这里不能保证在调用ContainsKey
and之间Add
,其他一些线程还没有将该键添加到字典中。
因此,线程安全类型可能需要比简单锁更多的东西。可能需要一种方法,如果它不存在的话,可以安全地将键和值添加到字典中,在原子操作中,然后返回一个标志告诉你它做了什么,可能需要。
事实上,看这里:ConcurrentDictionary.TryAdd。
我的建议是:
- 如果您需要这些保证,请通过需要安全和可预测的场景将类型设计为线程安全的,并专门实现这些
- 如果您不需要这些保证,请不要费心做任何事情,而只需记录该类型不是线程安全的,将其留给使用它的代码
.NET 类型List
不以任何方式使用锁,除了一些关注线程安全的选定位置,特别是SyncRoot 属性。
此外,编写良好的线程安全数据类型并不容易。只是在你需要的地方加锁可能会使它更加线程安全,但你会在性能方面付出巨大的代价。如果一个程序在使用它时不需要它是线程安全的,那么你仍然要付出很多代价。
编写高性能线程安全类型更难,并且通常不依赖锁(单独),而是使用自旋等待、特定 CPU 指令(其中一些可用于 .NET 代码)等。但这需要高度关于现代 CPU 如何执行代码的专业知识。
如果我是你,我会把线程安全留给专家,并从你自己的类型中删除它,除非你绝对需要它。