我有一个当前不需要线程安全的类,但将来我们可能想要创建一个线程安全的版本。我看待它的方式,我现在可以通过在相关函数周围放置锁来使其成为线程安全的,或者我现在可以将它们设置为虚拟,并在稍后的后代类中覆盖它们。也就是说,今天我可以这样做:
public void DoStuff()
{
lock (this.SyncRoot)
{
// Do stuff...
}
}
或者我可以这样做:
public virtual void DoStuff()
{
// Do stuff...
}
今天哪个选项可以更快地完成这些工作?
答案 0 :(得分:4)
虚函数调用基本上是数组查找加上间接函数调用。如果虚拟调用是在循环中发生的,即如果从同一实例上的相同位置多次调用虚函数调用,那么在大多数迭代中它将不比非内联正常函数调用慢。现代CPU分支预测器预测虚拟函数调用的目标,并且在获取函数的地址的同时推测性地执行该目标。
另一方面,锁定总是涉及至少一次或两次原子操作。这样的操作几乎可以保证在CPU管道上乱跑,因为它们需要内存屏障。答案 1 :(得分:2)
第二,因为虚拟通话非常便宜(锁也是一个额外的通话,这比虚拟通话本身更贵)。
此外,第二个让你在需要时完全实现锁定。
答案 2 :(得分:2)
如果你打算让DoStuff
同步(并保证它适用于任何给定的子类),那么你最好而不是使它virtual
和使用protected virtual
成员来完成实际工作。
public void DoStuff()
{
lock(this.SyncRoot)
{
InternalDoStuff();
}
}
protected virtual void InternalDoStuff()
{
// do stuff
}
这也为您提供了不 lock
在当前代码中的选项(意味着DoStuff
只调用InternalDoStuff
而没有其他代码),但仍然可以在以后将其放入,而无需触及您继承的代码。
至于速度,lock
语句的放置不会有任何影响。
答案 3 :(得分:1)
虚拟呼叫几乎肯定会更快。虚拟呼叫涉及额外的间接级别。锁通常涉及切换到内核模式 - 保守估计至少要慢100倍。
答案 4 :(得分:1)
第二个。如果您今天不需要线程安全,请不要这样做。公开SyncRoot
属性,以便将来可以将该方法设置为线程安全的。但请确保在文档中明确指出该方法不是线程安全的。
答案 5 :(得分:1)
Egads,我找不到引用,也许是在Joe Duffy的书中,但Lock可能是一个无操作,直到另一个线程开始命中它(即懒惰的创建)。
此外,此锁无论如何都不会进入内核模式,它基于Interlocked ### API。
最后,了解这些主题很有趣,但最好的办法是始终为自己的代码计时。
答案 6 :(得分:1)
我测试了VB SyncLock,它的速度差了近300倍。