【问题标题】:C# Does an empty lock block get "optimized" away?C# 空锁块是否被“优化”掉了?
【发布时间】: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


【解决方案1】:

不,他们没有。 CLI 不能假定没有其他线程已经锁定该对象。

如果任何其他线程(或您当前的代码)尝试使用非零线程锁定对象,对象标头或同步块表仍将被标记为线程 ID 和Monitor.Enter/Exit 上的非零递归计数id,如果需要,它将进入旋转等待或提升为基于内核的事件。

对于它的价值,因为您不关心重新排序,并且根据您的用例,可能还有其他 synchronization primitives 可能更适合您的特定用例。比如重置事件等。

【讨论】:

  • 嗯,我想知道是否有更好的同步选项。 AddListener 需要正常的锁定行为,而 _value 未设置,即使有 100 个线程在 AddListener 中竞争该锁定,Execute 真的只需要等待当前持有者退出,然后所有其他线程都可以中止锁。您认为其他选项中的哪一个更适合这种情况?
【解决方案2】:

根据sharplib,我们可以看到编译器不会删除空锁块。此外,我无法想象我们如何在真正的多线程环境中在编译时对其进行优化,并确保我们不会破坏任何东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-15
    • 2010-12-20
    • 2015-03-22
    • 2013-08-06
    • 2014-04-18
    • 2019-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多