互锁。比较交换<Int> 使用大于或小于而不是相等

Interlocked.CompareExchange<Int> using GreaterThan or LessThan instead of equality

本文关键字:小于 大于 交换 比较 lt Int 互锁 gt      更新时间:2023-10-16

System.Threading.Interlocked对象允许加法(减法)和比较作为原子操作。看来的比较换取只是不做平等,但作为原子比较的比较更大。

假设的Interlocked.GreaterThan是IL的功能,还是CPU级的功能?两者?

缺少任何其他选择,是否可以在C 中创建此类功能或直接IL代码,并将该功能暴露于C#?

您可以构建InterlockedCompareExchange的其他原子操作。

public static bool InterlockedExchangeIfGreaterThan(ref int location, int comparison, int newValue)
{
    int initialValue;
    do
    {
        initialValue = location;
        if (initialValue >= comparison) return false;
    }
    while (System.Threading.Interlocked.CompareExchange(ref location, newValue, initialValue) != initialValue);
    return true;
}

使用这些辅助方法,您不仅可以交换值,而且可以检测到它是否替换。

用法看起来像这样:

int currentMin = 10; // can be changed from other thread at any moment
int potentialNewMin = 8;
if (InterlockedExtension.AssignIfNewValueSmaller(ref currentMin, potentialNewMin))
{
    Console.WriteLine("New minimum: " + potentialNewMin);
}

这是方法:

public static class InterlockedExtension
{
    public static bool AssignIfNewValueSmaller(ref int target, int newValue)
    {
        int snapshot;
        bool stillLess;
        do
        {
            snapshot = target;
            stillLess = newValue < snapshot;
        } while (stillLess && Interlocked.CompareExchange(ref target, newValue, snapshot) != snapshot);
        return stillLess;
    }
    public static bool AssignIfNewValueBigger(ref int target, int newValue)
    {
        int snapshot;
        bool stillMore;
        do
        {
            snapshot = target;
            stillMore = newValue > snapshot;
        } while (stillMore && Interlocked.CompareExchange(ref target, newValue, snapshot) != snapshot);
        return stillMore;
    }
}

更新我在此处发表的文章:我们找到了一种更好的方法来通过使用其他锁定对象进行更大的比较。我们编写了许多单元测试,以证明可以一起使用锁和互锁,但仅在某些情况下使用。

代码的工作原理:互锁使用读或写入原子的内存屏障。需要同步锁,以使比较大于原子操作。因此,现在的规则是,在此类中,没有其他操作在没有此同步锁的情况下写入值。

我们从这个课程中获得的是一个互锁的值,可以很快读取,但写入需要更多。在我们的应用程序中,读取速度约为2-4倍。

在这里代码作为视图:

请参阅此处:http://files.thekieners.com/blogcontent/2012/exchangeifgreaterthan2.png

在这里作为代码复制&amp;粘贴:

public sealed class InterlockedValue
{
    private long _myValue;
    private readonly object _syncObj = new object();
    public long ReadValue()
    {
        // reading of value (99.9% case in app) will not use lock-object, 
        // since this is too much overhead in our highly multithreaded app.
        return Interlocked.Read(ref _myValue);
    }
    public bool SetValueIfGreaterThan(long value)
    {
        // sync Exchange access to _myValue, since a secure greater-than comparisons is needed
        lock (_syncObj)
        {
            // greather than condition
            if (value > Interlocked.Read(ref  _myValue))
            {
                // now we can set value savely to _myValue.
                Interlocked.Exchange(ref _myValue, value);
                return true;
            }
            return false;
        }
    }
}

这实际上不是正确的,但是将并发视为以两种形式出现是有用的:

  1. 锁免费并发
  2. 基于锁的并发

这是不正确的,因为基于软件锁的并发性最终是使用堆栈中某处(通常在内核中)的锁定原子指令实现的。但是,锁定免费的原子说明,最终都在内存总线上获取硬件锁。因此,实际上,锁定并发和基于锁的并发性是相同的。

但从概念上讲,在用户应用程序的级别上,它们是2种不同的做事方式。

基于锁定的并发基于"锁定"对代码关键部分的访问的想法。当一个线程"锁定"关键部分时,没有其他线程在同一关键部分中可能会运行代码。这通常是通过使用"静音"来完成的,"静音"与操作系统调度程序接口,并导致线程在等待进入锁定关键部分时变得不可能。另一种方法是使用"旋转锁",这会导致螺纹在循环中旋转,无用,直到临界部分可用为止。

免费锁定并发性是基于使用原子指令(特别由CPU支持)的想法,这些想法是由硬件保证的,可以在原子上运行。互锁。插入是免费锁定并发的一个很好的例子。它只是调用特殊的CPU指令,这些指令可以进行原子增量。

锁定并发很难。随着临界部分的长度和复杂性的增加,它变得特别困难。临界部分中的任何步骤都可以同时由任意数量的线程同时执行,并且它们可以以不同的速度移动。您必须确保尽管如此,整个系统的结果仍然正确。对于像增量之类的东西,它可以很简单(CS只是一个指令)。对于更复杂的关键部分,事情可能会很快变得非常复杂。

基于锁定的并发性也很难,但不如自由锁定并发能力那么难。它允许您创建代码的任意复杂区域,并且知道只有1个线程在任何时候执行。

锁定并发性具有一个很大的优势:速度。正确使用时,可能是基于锁定的并发的数量级。旋转循环对于长期运行的关键部分来说是不好的,因为它们浪费了CPU资源无济于事。静音对小关键部分可能不利,因为它们会引入很多开销。它们最少涉及模式开关,并且在最坏情况下进行多个上下文开关。

考虑实施托管堆。每次"新"都称为"新"将是可怕的。它将破坏您的应用程序的性能。但是,使用免费锁定并发性,可以使用互锁的增量实现Gen 0内存分配(我不确定这是CLR是否这样做,但是如果不是这样,我会感到惊讶。这可能是一个巨大的储蓄。

还有其他用途,例如在锁定的免费数据结构中,例如持久堆栈和AVL树。他们通常使用" CAS"(比较和交换)。

但是,基于锁定的并发和锁定并发性的原因确实是因为每个人的实现详细信息。

旋转锁通常在其循环条件下使用原子说明(通常是CAS)。静音需要在其实现中使用旋转锁或内部内核结构的原子更新。

原子指令依次使用硬件锁实施。

无论如何,他们俩都有贸易折扣集,通常以perf和复杂性为中心。静音既可以比锁定代码更快又慢。锁定的免费代码比互惠码更复杂。使用的适当机制取决于特定情况。

现在,回答您的问题:

进行互锁的方法比较交换,如果少于呼叫者不使用锁的呼叫者。您不能以相同的方式来实现它,或者可以进行比较交换。您可以通过循环中的互锁交换来模拟它进行减法(计算少于小于)。您也可以使用静音(但这意味着锁定,因此在名称中使用"互锁"会产生误导)。构建"通过CAS的模拟互锁"版本是否合适?那要看。如果该代码非常频繁地调用,并且线程争议很少,则答案是肯定的。如果不是,您可以将具有适中恒定因子的O(1)操作变成无限(或很长的)循环,在这种情况下,最好使用互斥。

大多数时候不值得。

您对此实现有何看法:

// this is a Interlocked.ExchangeIfGreaterThan implementation
private static void ExchangeIfGreaterThan(ref long location, long value)
{
    // read
    long current = Interlocked.Read(ref location);
    // compare
    while (current < value)
    {
        // set
        var previous = Interlocked.CompareExchange(ref location, value, current);
        // if another thread has set a greater value, we can break
        // or if previous value is current value, then no other thread has it changed in between
        if (previous == current || previous >= value) // note: most commmon case first
            break;
        // for all other cases, we need another run (read value, compare, set)
        current = Interlocked.Read(ref location);
    }
}

所有互锁操作都在硬件中具有直接的支持。

互锁操作和原子数据类型是不同的。原子类型是库级功能。在某些平台上,对于某些数据类型,原子是使用互锁指令实现的。在这种情况下,它们非常有效。

在其他情况下,当平台根本没有互锁操作或无法用于某些特定数据类型时,库使用适当的同步(Crit_sect,Mutex等)实现了这些操作。

我不确定是否真的需要Interlocked.GreaterThan。否则可能已经实施。如果您知道它有用的好示例,我相信这里的每个人都会很高兴听到此消息。

大/少于和等于已经是原子操作了。这无法解决您的应用程序的安全同时行为。

使他们成为互锁家庭的一部分是没有意义的,所以问题是:您实际上要实现什么?