我在这里回答是为了补充 Marc 的回答,他说“根据访问配置文件,还有很多不同的方法可以使某些东西成为线程安全的”。
我只是想补充一下,部分原因是有很多方法不是线程安全的,当我们说某事是线程安全的时,我们有明确提供了哪些安全性。
对于几乎所有可变对象,都会有处理它的方法不是线程安全的(注意几乎任何,都会出现异常)。考虑一个具有以下(线程安全)成员的线程安全队列;入队操作、出队操作和计数属性。通过对每个成员进行内部锁定,或者甚至使用无锁技术来构建其中一个相对容易。
但是,假设我们这样使用对象:
if(queue.Count != 0)
return queue.Dequeue();
上面的代码不是线程安全的,因为不能保证在(线程安全的)Count 返回 1 之后,另一个线程不会出队,从而导致第二次操作失败。
在许多方面它仍然是一个线程安全的对象,特别是即使在这种失败的情况下,失败的出队操作也不会使对象进入无效状态。
为了使一个对象在面对任何给定的操作组合时作为一个整体线程安全,我们必须使其在逻辑上不可变(它可能具有内部可变性,线程安全操作更新内部状态作为优化 - 例如通过记忆或根据需要从数据源加载,但在外部它必须看起来是不可变的)或严重减少可能的外部操作数量(我们可以创建一个只有Enqueue和TryDequeue的线程安全队列始终是线程安全的,但这既减少了可能的操作,又强制将失败的出队重新定义为不是失败的,并强制更改从我们之前的版本调用代码的逻辑)。
任何其他都是部分保证。我们免费获得了一些部分保证(正如 Marc 所指出的,就单个原子而言,对某些自动属性进行操作已经是线程安全的 - 在某些情况下,这就是我们需要的所有线程安全性,但在其他情况下不会去任何地方足够近了)。
让我们考虑一个属性,它将这种部分保证添加到我们尚未获得它的那些情况。它对我们有多大价值?好吧,在某些情况下它会是完美的,但在其他情况下则不会。回到我们在出队之前进行测试的情况,在Count 上有这样的保证并没有多大用处——我们有这个保证,并且代码在多线程条件下仍然失败,而在单线程条件下则不会.
更重要的是,将这种保证添加到还没有它的情况下至少需要一定程度的开销。一直担心开销可能是过早的优化,但是增加开销却没有收益是过早的悲观,所以我们不要这样做!更重要的是,如果我们确实提供更广泛的并发控制以使一组操作真正线程安全,那么我们将使更窄的并发控制变得无关紧要,它们变成纯粹的开销 - 所以我们甚至无法从我们的某些情况下的开销;这几乎总是纯粹的浪费。
也不清楚并发问题的范围有多大。我们是否只需要锁定(或类似)该属性,还是需要锁定所有属性?我们是否还需要锁定非自动操作,这可能吗?
这里没有好的单一答案(在推出您自己的解决方案时,它们可能是难以回答的问题,更不用说尝试在其他人使用此 [Threadsafe] 属性时会生成此类代码的代码中回答它) .
此外,任何给定的方法都会有一组不同的条件,在这些条件下可能会发生死锁、活锁和类似问题,因此我们实际上可以通过将线程安全视为我们可以盲目应用于属性的东西来降低线程安全.
如果无法找到这些问题的单一通用答案,就没有提供单一通用实现的好方法,并且任何此类 [Threadsafe] 属性充其量只能具有非常有限的价值。最后,在使用它的程序员的心理层面上,很可能会导致一种错误的安全感,即他们已经创建了一个线程安全的类,而实际上他们并没有;这实际上会使它变得比无用更糟。