例如,.NET静态字段不是线程安全的,如果它们默认情况下是线程安全的,结果会怎样呢?(无需执行“手动”锁定)。使用(实际上默认为)非线程安全有什么好处?
我想到的一件事是性能(尽管更多的是猜测)。当函数或字段不需要线程安全时,直觉告诉我们不应该使用线程安全。然而,问题是:为什么?线程安全只是你始终需要实现的额外代码吗?在哪些场景中可以100%确定例如一个字段不会被两个线程同时使用?
编写线程安全的代码:
但是!并不总是需要编写线程安全的代码。如果你可以确定某个代码片段只会被一个线程访问,上述列表就变得很冗余且不必要。这就像两个人不携带太多行李时去邻近城市,租一辆货车一样。
线程安全性是有代价的 - 如果同时访问可能引起问题的字段,您需要进行锁定。
在没有使用线程但需要高性能且每个 CPU 循环都非常重要的应用程序中,无需使用安全线程类。
那么,保留非线程安全的代码的意义是什么呢?
成本。就像你所想象的那样,通常会降低性能。
此外,编写线程安全的代码更加困难和耗时。
线程安全不是一个“是”或“否”的问题。 “线程安全”的含义取决于上下文; 它是否意味着“并发读取安全,但并发写入不安全”? 它是否意味着应用程序可能返回陈旧的数据而不是崩溃? 它可以有很多含义。
不将类设为“线程安全”的主要原因是成本。 如果该类型不会被多个线程访问,则没有将其设置为线程安全的优势,并且会增加维护成本。
写线程安全的代码有时非常痛苦。例如,简单的延迟加载需要两次检查“==null”和一个锁。很容易出错。
[编辑]
我并不是想表达多线程延迟加载特别困难,而是在你认为已经完成了锁定之后,“哦,我忘记锁定那个了!”这些瞬间会迅速而且频繁地出现,这才是真正的挑战。
有些情况下,“线程安全”并不适用。这个考虑因素除了需要更高的开发者技能和增加的时间(开发、测试和运行时都会受到影响)。
例如,List<T>
是一个常用的非线程安全类。如果我们要创建一个线程安全的等效类,我们该如何实现 GetEnumerator
?提示:没有好的解决方案。
List<T>
)实际上无法变成线程安全的。你可以创建其他线程安全的集合(例如ConcurrentQueue<T>
),或者选择使其不可变,但是你不能拥有一个与非线程安全的List<T>
具有相同语义的线程安全的List<T>
。 - Stephen ClearyList<T>
进行索引操作将无法有意义地保证线程安全。但是,可以拥有有用的线程安全子集List<T>
的功能,包括例如所有不删除项目的成员,或包括Add
、Remove
、RemoveAt(0)
、Item(0).Get
和GetEnumerator
,但没有其他索引操作(缺少读取项0的Try
方法会让人感到恼火,但并非不可克服)。 - supercatDoWork()
的方法。他在其中调用其他类Read
两次,Write
两次,然后是LogToSteam
。那么,保留非线程安全代码的意义是什么?
原则上,尽可能避免使用锁。理想情况下,代码应该是可重入的和线程安全的,不需要任何锁定。但这只是空想。
回归现实,一个好的程序员尽其所能采用局部锁定而不是锁住整个上下文。例如,在各种例程中每次锁定几行代码,而不是在函数中锁定所有东西。
因此,还必须重构代码,以设计最小化锁定、甚至消除锁定的方案。
例如,考虑一个foobar()
函数,它在每次调用时获取新数据,并在树中更改节点的数据类型上使用switch() case
。可以大多数情况下避免锁定(如果不是完全),因为每个case语句将触摸树中的不同节点。尽管这是一个更具体的例子,但我认为它阐明了我的观点。