Threadsafe Lazy Class - PullRequest
       14

Threadsafe Lazy Class

1 голос
/ 02 декабря 2009

У меня есть класс Lazy, который лениво вычисляет выражение:

public sealed class Lazy<T>
{
    Func<T> getValue;
    T value;

    public Lazy(Func<T> f)
    {
        getValue = () =>
            {
                lock (getValue)
                {
                    value = f();
                    getValue = () => value;
                }
                return value;
            };
    }

    public T Force()
    {
        return getValue();
    }
}

По сути, я пытаюсь избежать накладных расходов на блокировку объектов после их оценки, поэтому я заменяю getValue другой функцией при вызове.

Это очевидно работает в моем тестировании, но я не могу знать, взорвется ли он в производстве.

Мой класс безопасен для потоков? Если нет, что можно сделать, чтобы гарантировать безопасность потока?

Ответы [ 4 ]

2 голосов
/ 02 декабря 2009

Ваш код имеет несколько проблем:

  1. Вам нужен один объект для блокировки. Не блокируйте переменную, которая изменяется - блокировки всегда имеют дело с объектами, поэтому, если getValue изменяется, несколько потоков могут одновременно войти в заблокированный раздел.

  2. Если несколько потоков ожидают блокировки, все они будут оценивать функцию f () друг за другом. Вы должны проверить внутри блокировки, что функция еще не была оценена.

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

Однако вместо этого я бы использовал подход флага от Конрада Рудольфа (просто убедитесь, что вы не забудете «volatile», необходимый для этого). Таким образом, вам не нужно вызывать делегата всякий раз, когда извлекается значение (вызовы делегата выполняются довольно быстро; но не так быстро, как простая проверка bool).

2 голосов
/ 02 декабря 2009

Разве вы не можете просто пропустить переоценку функции полностью, используя флаг или защитное значение для реального значения? I.e.:

public sealed class Lazy<T>
{
    Func<T> f;
    T value;
    volatile bool computed = false;
    void GetValue() { lock(LockObject) { value = f();  computed = true; } }

    public Lazy(Func<T> f)
    {
        this.f = f;
    }

    public T Force()
    {
        if (!computed) GetValue();
        return value;
    }
}
0 голосов
/ 02 декабря 2009

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

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

Thread 1
Thread 2
Thread 1 completes

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

Хотя я не совсем уверен, что это будет делать (кроме выполнения синхронизированной оценки выражения и кэширования результата), это должно сделать его более безопасным:

public sealed class Lazy<T>
{
    Func<T> getValue;
    T value;
    object lockValue = new object();

    public Lazy(Func<T> f)
    {
        getValue = () =>
            {
                lock (lockValue)
                {
                    value = f();
                    getValue = () => value;
                }
                return value;
            };
    }

    public T Force()
    {
        return getValue();
    }
}
0 голосов
/ 02 декабря 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...