1

我有一个当前不需要线程安全的类,但将来我们可能想要制作一个线程安全的版本。在我看来,我现在可以通过在相关函数周围加锁来使其成为线程安全的,或者我现在可以使它们成为虚拟的,然后在后代类的覆盖中加锁。也就是说,今天我可以这样做:

public void DoStuff()
{
    lock (this.SyncRoot)
    {
        // Do stuff...
    }
}

或者我可以这样做:

public virtual void DoStuff()
{
    // Do stuff...
}

今天,哪个选项可以更快地完成工作?

4

7 回答 7

4

虚函数调用基本上是数组查找加上间接函数调用。如果虚拟调用发生在循环中,即如果虚拟函数调用在同一个实例的同一位置被多次调用,那么在大多数迭代中,它不会比非内联的普通函数调用慢。现代 CPU 分支预测器预测虚拟函数调用的目标,并推测性地执行此目标,同时获取函数的地址。

另一方面,锁总是在幕后至少涉及一两个原子操作。这样的操作几乎肯定会对 CPU 管道造成严重破坏,因为它们需要内存屏障。

于 2010-01-21T16:18:42.097 回答
2

第二,因为虚拟调用非常便宜(锁也是一个额外的调用,这将比虚拟调用本身更昂贵)。

此外,第二个让您在需要时准确地实施锁定。

于 2010-01-21T16:02:01.890 回答
2

如果您打算进行DoStuff同步(并保证它适用于任何给定的子类),那么最好不要进行同步virtual并使用protected virtual成员来完成实际工作。

public void DoStuff()
{
    lock(this.SyncRoot)
    {
        InternalDoStuff();
    }
}

protected virtual void InternalDoStuff()
{
    // do stuff
}

这也为您提供了不在 lock当前代码中的选项(意味着DoStuff仅在没有其他代码的情况下调用InternalDoStuff),但仍然可以在以后将其滑入,而无需触及您继承的代码。

至于速度,lock语句的位置不会有任何影响。

于 2010-01-21T16:03:54.600 回答
1

虚拟通话几乎肯定会更快。虚拟调用涉及额外的间接级别。锁通常涉及到内核模式的切换——保守估计至少要慢 100 倍。

于 2010-01-21T16:03:54.303 回答
1

第二。如果您今天不需要线程安全,请不要这样做。公开该SyncRoot属性,以便将来该方法可以变成线程安全的。但请确保在文档中明确说明该方法不是线程安全的。

于 2010-01-21T16:04:16.633 回答
1

Egads,我找不到参考,也许它在 Joe Duffy 的书中,但 Lock 可能是一个无操作,直到另一个线程开始点击它(即懒惰创建)。

此外,此锁无论如何都不会进入内核模式,它基于 Interlocked### API。

最后,思考这些主题很有趣,但最好的办法是始终为自己的代码计时。

于 2010-01-21T16:32:46.277 回答
1

我测试了 VB SyncLock,它几乎慢了 300 倍。

于 2010-01-22T21:37:42.823 回答