【发布时间】:2021-01-31 07:22:56
【问题描述】:
我找不到任何关于 C# 编译器或 JIT 是否会删除内部没有代码的锁定语句的信息。这会始终生成并执行Monitor.Enter 和Monitor.Exit 调用吗?
lock(lockObj) { }
我正在尝试做的事情的(大大简化)版本(是的,我知道在锁中调用回调是不好的):
public class ExecutionSource
{
private List<Action<object>> _callbacks = new List<Action<object>>();
private object _value;
public void AddListener(Action<object> listener)
{
object temp = _value;
if (temp != null)
{
listener(temp);
return;
}
lock (_callbacks)
{
temp = _value;
if (temp != null)
{
listener(temp);
}
else
{
_callbacks.Add(listener);
}
}
}
public void Execute(object value)
{
if (value == null) throw new InvalidOperationException("value must be non-null");
if (Interlocked.CompareExchange(ref _value, value, null) != null)
{
throw new InvalidOperationException("Can only execute once.");
}
lock (_callbacks) { } // Wait for a listener that is currently being added on another thread. No need to lock the entire loop.
foreach (var callback in _callbacks)
{
callback(value);
}
_callbacks.Clear();
}
}
【问题讨论】:
-
我看不出它是如何被“优化”掉的,因为它会极大地改变语义——如果另一个线程已经拥有锁(并且永远不会释放它),那么空锁块 应该有效地停止执行它的线程。也就是说,我看不出你的空锁块是如何完成评论所表达的......
-
我将添加
lock的功能比它看起来的要多,包括memory barrier。 -
@JonSkeet 它通过
AddListener锁定,然后检查值来完成评论所说的,而Execute设置值,然后锁定。如果在调用Execute时多个线程正在等待AddListener中的锁,则可能只有1 个线程会添加到列表中。当任何其他线程获取锁时,它们将看到该值已设置(感谢锁中的内存屏障)。任何进一步的调用都不会锁定...当然,假设发送到Execute的值是非空的。 -
@Tim:不,它没有做到这一点 - 因为如果没有任何东西已经拥有锁,它根本不会等待。如果您创建
ExecutionSource的实例并调用Execute(并且什么都不做),它只会将_value设置为传递的任何值,获取并释放锁,然后执行空的回调列表。它将不等待将侦听器添加到另一个线程,这就是评论所说的它会做的事情。 -
是的,所以这不是线程安全的。根据您对我的回答的评论,您可能希望使用
ReaderWriterLock,它将支持单个作者和多个读者。
标签: c# locking compiler-optimization