C# 带锁的函数调用和虚拟调用哪个更快?
我有一个类,目前不需要是线程安全的,但在未来,我们可能会想要一个线程安全的版本。在我看来,我现在可以通过在相关函数周围放置锁来实现线程安全,或者现在可以将它们设置为虚拟函数,并在以后的子类中的重写中在它们周围放置锁。也就是说,今天我可以这样做:C# 带锁的函数调用和虚拟调用哪个更快?,c#,.net,performance,locking,virtual-functions,C#,.net,Performance,Locking,Virtual Functions,我有一个类,目前不需要是线程安全的,但在未来,我们可能会想要一个线程安全的版本。在我看来,我现在可以通过在相关函数周围放置锁来实现线程安全,或者现在可以将它们设置为虚拟函数,并在以后的子类中的重写中在它们周围放置锁。也就是说,今天我可以这样做: public void DoStuff() { lock (this.SyncRoot) { // Do stuff... } } 或者我可以这样做: public virtual void DoStuff()
public void DoStuff()
{
lock (this.SyncRoot)
{
// Do stuff...
}
}
或者我可以这样做:
public virtual void DoStuff()
{
// Do stuff...
}
现在,哪种方法可以更快地完成任务?第二种方法,因为虚拟调用非常便宜,锁也是一种附加调用,比虚拟调用本身更昂贵
另外,第二种方法让您在需要时完全实现锁定。第二种方法,因为虚拟调用非常便宜,所以锁定也是一种附加调用,比虚拟调用本身更昂贵
另外,第二种方法让您在需要时完全实现锁定。虚拟调用几乎肯定会更快。虚拟调用涉及额外级别的间接寻址。锁通常需要切换到内核模式——保守估计至少慢100倍。虚拟调用几乎肯定会更快。虚拟调用涉及额外级别的间接寻址。锁通常涉及到切换到内核模式-保守估计至少慢100倍。如果您打算使DoStuff同步并保证它适用于任何给定的子类,那么最好不要将其设置为虚拟,而是使用受保护的虚拟成员来执行实际工作
public void DoStuff()
{
lock(this.SyncRoot)
{
InternalDoStuff();
}
}
protected virtual void InternalDoStuff()
{
// do stuff
}
这还为您提供了不锁定当前代码的选项,这意味着DoStuff只需调用InternalDoStuff,而无需使用其他代码,但仍然能够在以后插入它,而无需接触继承的代码
至于速度,lock语句的位置不会有任何影响。如果您打算使DoStuff同步并保证它适用于任何给定的子类,那么最好不要将其设置为虚拟,而是使用受保护的虚拟成员来执行实际工作
public void DoStuff()
{
lock(this.SyncRoot)
{
InternalDoStuff();
}
}
protected virtual void InternalDoStuff()
{
// do stuff
}
这还为您提供了不锁定当前代码的选项,这意味着DoStuff只需调用InternalDoStuff,而无需使用其他代码,但仍然能够在以后插入它,而无需接触继承的代码
至于速度,lock语句的位置不会有任何影响。第二个。如果你今天不需要线程安全,就不要这样做。公开SyncRoot属性,以便将来该方法可以变成线程安全的。但是请确保在文档中清楚地说明该方法不是线程安全的。第二个。如果你今天不需要线程安全,就不要这样做。公开SyncRoot属性,以便将来该方法可以变成线程安全的。但请确保在文档中明确说明该方法不是线程安全的。虚拟函数调用基本上是数组查找加上间接函数调用。如果虚拟调用发生在循环中,即,如果虚拟函数调用在同一实例上的同一位置被多次调用,那么在大多数迭代中,它不会比非内联正常函数调用慢。现代CPU分支预测器预测虚拟函数调用的目标,并在获取函数地址的同时推测性地执行该目标
另一方面,一把锁在引擎盖下至少需要一两次原子操作。这样的操作几乎肯定会在CPU管道上造成破坏,因为它们需要内存屏障。虚拟函数调用基本上是数组查找加上间接函数调用。如果虚拟调用发生在循环中,即,如果虚拟函数调用在同一实例上的同一位置被多次调用,那么在大多数迭代中,它不会比非内联正常函数调用慢。现代CPU分支预测器预测虚拟函数调用的目标,并在获取函数地址的同时推测性地执行该目标
另一方面,一把锁在引擎盖下至少需要一两次原子操作。这样的操作几乎肯定会在CPU管道上造成破坏,因为它们需要内存障碍。Egads,我找不到参考资料,也许它在Joe Duffy的书中,但在另一个线程开始攻击锁之前,锁可能是不起作用的,即延迟创建 此外,该锁无论如何都不会进入内核模式,它基于互锁API
最后,思考这些主题是很有趣的,但最好的办法是始终为自己的代码计时。Egads,我找不到参考资料,也许它在Joe Duffy的书中,但锁定可能是无效的,直到另一个线程开始攻击它,即惰性创建 此外,此锁不能进入ke 无论如何,rnel模式是基于联锁API的
最后,思考这些话题很有趣,但最好的办法是始终为自己的代码计时。我测试了VB SyncLock,它几乎慢了300倍。我测试了VB SyncLock,它几乎慢了300倍。SyncRoot属性是我只为线程安全版本添加的东西-我应该在问题中更清楚地说明这一点。SyncRoot属性是我只想为线程安全版本添加一些东西——我应该在问题中说得更清楚一些。假设普通情况是无争议的,那么不,至少在Linux和Windows上的锁并不意味着进入内核。然而,现在获取和释放锁需要处理器执行内存屏障指令,这通常是非常昂贵的数百个周期,取决于硬件…内存屏障会降低多CPU和多核效率。假设常见情况是无争用的,那么没有,至少在Linux和Windows上的锁并不意味着进入内核。然而,现在获取和释放锁需要处理器执行内存屏障指令,这通常是非常昂贵的数百个周期,取决于硬件…内存屏障会降低多CPU和多核效率。这里的两个示例都是虚拟的。这就是目的吗?这里的两个例子都是虚拟的。这就是目的吗?