Как атомарно поменять 2 дюйма в C#?

Что (если есть) является эквивалентом C# ассемблера x86 xchg инструкция?

С этой командой, которая IMO является подлинным обменом (в отличие от Interlocked.Exchange), Я мог бы просто атомарно поменять два целых, что я действительно пытаюсь сделать.

Обновить:

Пример кода на основе моего предложения. Переменные с суффиксом "_V" оформляются как volatile:

// PART 3 - process links
// prepare the new Producer
address.ProducerNew.WorkMask_V = 0;
// copy the current LinkMask
address.ProducerNew.LinkMask_V = address.Producer.LinkMask_V;
// has another (any) thread indicated it dropped its message link from this thread?
if (this.routerEmptyMask[address.ID] != 0)
{
  // allow all other bits to remain on (i.e. turn off now defunct links)
  address.ProducerNew.LinkMask_V &= ~this.routerEmptyMask[address.ID];
  // reset
  this.routerEmptyMask[address.ID] = 0;
}
// PART 4 - swap
address.ProducerNew = Interlocked.Exchange<IPC.Producer>(ref address.Producer, address.ProducerNew);
// PART 5 - lazily include the new links, make a working copy
workMask = address.Producer.LinkMask_V |= address.ProducerNew.WorkMask_V;

Обратите внимание на ленивое обновление.

8 ответов

Решение

Вот такая странная идея. Я не знаю точно, как у вас настроена структура данных. Но если это возможно, вы можете хранить свои два int значения в long тогда я думаю, что вы можете поменять их местами.

Например, предположим, что вы обернули свои два значения следующим образом:

class SwappablePair
{
    long m_pair;

    public SwappablePair(int x, int y)
    {
        m_pair = ((long)x << 32) | (uint)y;
    }

    /// <summary>
    /// Reads the values of X and Y atomically.
    /// </summary>
    public void GetValues(out int x, out int y)
    {
        long current = Interlocked.Read(ref m_pair);

        x = (int)(current >> 32);
        y = (int)(current & 0xffffffff);
    }

    /// <summary>
    /// Sets the values of X and Y atomically.
    /// </summary>
    public void SetValues(int x, int y)
    {
        // If you wanted, you could also take the return value here
        // and set two out int parameters to indicate what the previous
        // values were.
        Interlocked.Exchange(ref m_pair, ((long)x << 32) | (uint)y);
    }
}

Тогда, кажется, вы могли бы добавить следующее Swap метод, который приводит к обмену пары "атомарно" (на самом деле, я не знаю, справедливо ли на самом деле говорить, что следующее является атомарным; это больше похоже на то, что он дает тот же результат, что и атомный обмен).

/// <summary>
/// Swaps the values of X and Y atomically.
/// </summary>
public void Swap()
{
    long orig, swapped;
    do
    {
        orig = Interlocked.Read(ref m_pair);
        swapped = orig << 32 | (uint)(orig >> 32);
    } while (Interlocked.CompareExchange(ref m_pair, swapped, orig) != orig);
}

Вполне возможно, что я реализовал это неправильно, конечно. И в этой идее может быть недостаток. Это просто идея.

Это вероятная реализация Interlocked.Exchange() в CLR, скопированная из источника SSCLI20:

Обратите внимание, что UP в имени функции означает UniProcessor. Это не атомарно в SMP / многоядерных системах. Эта реализация будет использоваться CLR только в одноядерных системах.

FASTCALL_FUNC ExchangeUP,8
        _ASSERT_ALIGNED_4_X86 ecx
        mov     eax, [ecx]      ; attempted comparand
retry:
        cmpxchg [ecx], edx
        jne     retry1          ; predicted NOT taken
        retn
retry1:
        jmp     retry
FASTCALL_ENDFUNC ExchangeUP

Это лучше, чем использование XCHG, потому что этот код работает без блокировки шины. xchg имеет неявное lock приставка, так непохожая xadd или же cmpxchg просто нельзя пропустить, чтобы одноядерные системы по-прежнему выполняли операцию в одной инструкции, чтобы сделать ее атомарной по отношению к прерываниям (и, таким образом, другим потокам в однопроцессорных системах).

Странно выглядящий код перехода является оптимизацией в случае, если данные предсказания ветвления недоступны. Само собой разумеется, что попытка сделать лучшую работу, чем то, о чем много лет говорили очень хорошие инженеры-программисты с щедрой помощью производителей микросхем, является сложной задачей.

Почему нет Interlocked.Exchange подходит для вас?

Если вам требуется поменять местами точную память, вы используете неправильный язык и платформу, так как.NET абстрагирует управление памятью, так что вам не нужно об этом думать.

Если вы должны сделать что-то подобное без Interlocked.Exchange Вы можете написать код, помеченный как unsafe и выполните традиционный обмен на основе указателей, как в C или C++, но вам нужно обернуть его в подходящий контекст синхронизации, чтобы это была атомарная операция.

Обновить
Вам не нужно прибегать к unsafe код, чтобы сделать своп атомарно. Вы можете обернуть код в контекст синхронизации, чтобы сделать его атомарным.

lock (myLockObject)
{
  var x = Interlocked.Exchange(a, b);
  Interlocked.Exchange(b, x);
}

Обновление 2
Если синхронизация не является вариантом (как указано в комментариях), то я считаю, что вам не повезло. Поскольку вы гоняетесь за какой-то неизмеримой эффективностью, вы можете сконцентрироваться в другом месте. Если замена двух целочисленных значений сильно снижает производительность, возможно, вы используете не ту платформу.

Interlocked.Exchange - это действительно единственное, что вы можете сделать:

  var x = Interlocked.Exchange(a, b);
  Interlocked.Exchange(b, x);

Вы правы, что это не будет атомарным, но с использованием локальной переменной вам гарантировано, что значения согласованы, пока выполняются обе строки. Другими вашими вариантами являются небезопасный код (для использования указателей), использование p/invoke для собственной библиотеки или редизайн, так что он больше не требуется.

Согласно MSDN, Interlocked.Exchange является атомарным.

Если он вам не подходит, вы можете реализовать XCHG в небезопасном разделе, используя C/C++.

Interlocked.Exchange - это лучший способ поменять два значения int потокобезопасным способом в C#.

Используйте этот класс, даже если у вас есть многопроцессорный компьютер, и вы никогда не знаете, на каком процессоре будет работать ваш поток.

Вне Interlocked.Exchange, я предполагаю, что команда XCHG, вероятно, является реализацией свопа XOR, так что вы можете написать свой собственный.

Синтаксис C: (из ссылки на Википедию)

 void xorSwap (int *x, int *y) {
     if (x != y) {
         *x ^= *y;
         *y ^= *x;
         *x ^= *y;
     }
 }

Редактировать это не атомарно, вам придется синхронизировать его самостоятельно

Я думаю, что я только что нашел лучшее решение. Это:

Метод Interlocked.Exchange() (ref T, T)

Все "новые" переменные могут быть установлены в классе (Of T) и заменены текущими переменными. Это позволяет создавать атомарный снимок, одновременно заменяя любое количество переменных.

Другие вопросы по тегам