Почему Interlocked.CompareExchange <T>поддерживает только ссылочные типы? - PullRequest
7 голосов
/ 11 февраля 2010

Отказ от ответственности: Мои сообщения, очевидно, всегда многословны. Если вам случается знать ответ на заглавный вопрос, не стесняйтесь просто ответить на него, не читая мою расширенную дискуссию ниже.


Класс System.Threading.Interlocked предоставляет несколько очень полезных методов, помогающих писать поточно-ориентированный код. Одним из более сложных методов является CompareExchange, который можно использовать для вычисления промежуточного итога, который можно обновлять из нескольких потоков.

Поскольку использование CompareExchange немного сложнее, я подумал, что довольно разумно представить некоторые вспомогательные методы для него:

// code mangled so as not to require horizontal scrolling
// (on my monitor, anyway)
public static double Aggregate
(ref double value, Func<double, double> aggregator) {
    double initial, aggregated;

    do {
        initial = value;
        aggregated = aggregator(initial);
    } while (
        initial != Interlocked.CompareExchange(ref value, aggregated, initial)
    );

    return aggregated;
}

public static double Increase(ref double value, double amount) {
    return Aggregate(ref value, delegate(double d) { return d + amount; });
}

public static double Decrease(ref double value, double amount) {
    return Aggregate(ref value, delegate(double d) { return d - amount; });
}

Теперь, возможно, я просто виновен в том, что я счастлив от дженериков (я признаю, что это часто так); но для меня глупо ограничивать функциональность, предоставляемую вышеуказанными методами, только double значениями (или, точнее, мне приходится писать перегруженные версии вышеуказанных методов для каждого типа I) хочу поддержать). Почему я не могу это сделать?

// the code mangling continues...
public static T Aggregate<T>
(ref T value, Func<T, T> aggregator) where T : IEquatable<T> {
    T initial, aggregated;

    do {
        initial = value;
        aggregated = aggregator(initial);
    } while (
        !initial.Equals(
            Interlocked.CompareExchange<T>(ref value, aggregated, initial)
        )
    );
}

Я не могу этого сделать, потому что Interlocked.CompareExchange<T>, очевидно, имеет ограничение where T : class, а Я не понимаю, почему . Я имею в виду, , может быть , потому что уже существуют перегрузки для CompareExchange, которые принимают Int32, Int64, Double и т. Д .; но это вряд ли кажется хорошим обоснованием. В моем случае, например, было бы очень удобно иметь возможность использовать метод Aggregate<T> для выполнения широкого спектра атомных расчетов.

Ответы [ 3 ]

12 голосов
/ 11 февраля 2010

Interlocked.CompareExchange предназначен для реализации с собственными атомарными инструкциями, предоставляемыми непосредственно процессором. Бессмысленно использовать что-то подобное внутри lock (оно разработано для сценариев без блокировок).

Процессоры, предоставляющие команду атомарного обмена, естественно, поддерживают ее как небольшие операции «размером с регистр» (например, самая большая команда сравнения-обмена на процессоре Intel x64 - cmpxchg16b, которая работает на 128-битных значениях)

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

1 голос
/ 11 февраля 2010

Потому что эта перегрузка специально предназначена для сравнения и обмена ссылками. Он не выполняет проверку на равенство с использованием метода Equals (). Поскольку тип значения никогда не будет иметь ссылочного равенства со значением, с которым вы сравниваете его, я предполагаю, что они ограничивают T классом, чтобы предотвратить злоупотребление.

0 голосов
/ 11 февраля 2010

Я бы заподозрил, что Interlocked.CompareExchange<T> просто выполняет атомную подкачку указателя под капотом.

Попытка сделать это с типом значения, скорее всего, не даст ожидаемых результатов.

Вы можете, конечно, ввести значения в поле object перед их использованием.

...