27

System.Threading.Interlocked对象允许将加法(减法)和比较作为原子操作。似乎不做相等但也将 GreaterThan/LessThan 作为原子比较的 CompareExchange 将非常有价值。

是 IL的假设Interlocked.GreaterThan特性还是 CPU 级别的特性?两个都?

缺少任何其他选项,是否可以在 C++ 或直接 IL 代码中创建这样的功能并将该功能公开给 C#?

4

7 回答 7

57

您可以使用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;
}
于 2012-10-24T20:09:46.380 回答
6

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

用法如下所示:

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;
    }
}
于 2017-02-09T13:27:34.763 回答
3

你如何看待这个实现:

// 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);
    }
}
于 2012-11-10T15:05:05.853 回答
3

更新我在这里发表的后一篇文章:我们找到了一种更好的方法,通过使用额外的锁定对象来进行更大的比较。我们编写了许多单元测试来验证锁和互锁可以一起使用,但仅限于某些情况。

代码如何工作:Interlocked 使用读取或写入原子的内存屏障。需要同步锁来使大于比较成为原子操作。所以现在的规则是在这个类内部没有其他操作写入没有这个同步锁的值。

我们从这个类中得到的是一个互锁的值,它可以非常快速地读取,但写入需要更多的时间。在我们的应用程序中,读取速度大约快 2-4 倍。

这里的代码作为视图:

见这里: http: //files.thekieners.com/blogcontent/2012/ExchangeIfGreaterThan2.png

这里作为复制和粘贴的代码:

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;
        }
    }
}
于 2012-11-15T06:46:51.470 回答
2

这实际上不是真的,但将并发视为两种形式是很有用的:

  1. 无锁并发
  2. 基于锁的并发

这不是真的,因为基于软件锁的并发最终是使用堆栈上某处(通常在内核中)的无锁原子指令来实现的。然而,无锁原子指令最终都会在内存总线上获得硬件锁。因此,实际上,无锁并发和基于锁的并发是相同的。

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

基于锁的并发基于“锁定”对关键代码部分的访问的想法。当一个线程“锁定”了一个临界区时,没有其他线程可以在同一临界区内运行代码。这通常是通过使用“互斥锁”来完成的,它与操作系统调度程序接口并导致线程在等待进入锁定的临界区时变得不可运行。另一种方法是使用“自旋锁”,它会导致线程在循环中自旋,不做任何有用的事情,直到临界区可用。

无锁并发基于使用原子指令(CPU 特别支持)的思想,由硬件保证原子运行。Interlocked.Increment 是无锁并发的一个很好的例子。它只是调用执行原子增量的特殊 CPU 指令。

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

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

然而,无锁并发有一个很大的优势:速度。如果使用得当,它可以比基于锁的并发快几个数量级。自旋循环不利于长时间运行的关键部分,因为它们无所事事地浪费 CPU 资源。互斥锁对于小的关键部分可能不好,因为它们会引入大量开销。它们至少涉及模式切换,最坏情况下涉及多个上下文切换。

考虑实施托管堆。每次调用“新”时调用操作系统将是可怕的。它会破坏您的应用程序的性能。但是,使用无锁并发可以使用互锁增量来实现第 0 代内存分配(我不确定 CLR 是否这样做,但如果不是,我会感到惊讶。这可能是一个巨大的储蓄。

还有其他用途,例如无锁数据结构,如持久堆栈和 avl 树。他们通常使用“cas”(比较和交换)。

然而,基于锁定的并发和无锁并发实际上是等价的,原因在于它们各自的实现细节。

自旋锁通常在其循环条件中使用原子指令(通常是 cas)。互斥锁需要在其实现中使用自旋锁或内部内核结构的原子更新。

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

无论如何,它们都有自己的权衡取舍,通常以性能与复杂性为中心。互斥锁可以比无锁代码更快也可以更慢。无锁代码可以比互斥锁更复杂也可以更简单。使用的适当机制取决于具体情况。

现在,回答你的问题:

如果小于,则执行互锁比较交换的方法将暗示调用者它没有使用锁。你不能用一条指令来实现它,就像增量或比较交换一样。您可以模拟它做减法(计算小于),并在循环中进行互锁比较交换。您也可以使用互斥锁来执行此操作(但这意味着锁定,因此在名称中使用“互锁”会产生误导)。构建“通过cas模拟联锁”版本是否合适?那要看。如果代码被非常频繁地调用,并且线程争用很少,那么答案是肯定的。如果没有,您可以将具有中等高常数因子的 O(1) 操作转换为无限(或非常长)循环,在这种情况下,最好使用互斥锁。

大多数时候不值得。

于 2012-10-24T03:28:40.143 回答
1

所有联锁操作在硬件中都有直接支持。

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

在其他情况下,当平台根本没有互锁操作或它们对某些特定数据类型不可用时,库使用适当的同步(crit_sect、互斥锁等)来实现这些操作。

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

于 2012-10-24T02:17:14.860 回答
0

大于/小于和等于已经是原子操作。这并没有解决您的应用程序的安全并发行为。

让他们成为 Interlocked 家庭的一部分是没有意义的,所以问题是:你真正想要实现什么?

于 2012-10-24T02:17:04.643 回答