Упрощение блокировки в MTA - PullRequest
       6

Упрощение блокировки в MTA

1 голос
/ 05 октября 2008

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

Чтобы избежать повторения создания объекта для блокировки и записи нескольких операторов блокировки через код, я создал универсальный класс для обработки блокировки.

Я что-то упускаю, концептуально? Это должно работать, верно?

public class Locked<T> where T : new()
{
    private readonly object locker = new object();

    private T value;

    public Locked()
        : this(default(T))
    { }

    public Locked(T value)
    {
        this.value = value;
    }

    public T Get()
    {
        lock (this.locker)
        {
            return this.value;
        }
    }

    public void Set(T value)
    {
        lock (this.locker)
        {
            this.value = value;
        }
    }    
}

И пример его использования в классе:

private Locked<bool> stopWorkerThread = new Locked<bool>();

public void WorkerThreadEntryPoint()
{
    while (true)
    {
        if (this.stopWorkerThread.Get())
        {
            break;

}

Кроме того, как бы я протестировал что-то подобное в автоматическом режиме (например, создать юнит-тест)?

Наконец, что я могу сделать, чтобы реализовать оператор ++ и -, чтобы избежать этого:

        this.runningThreads.Set(this.runningThreads.Get() + 1);

Ответы [ 2 ]

3 голосов
/ 05 октября 2008

Это блокирует только на время получения / установки; конечно, во многих общих случаях это все равно будет атомарным, просто из-за размера данных.

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

Для чего-то такого простого, как bool, "volatile" может решить проблему намного проще - особенно если это просто для выхода из цикла.

Возможно, вы также захотите рассмотреть [MethodImpl (MethodImplOptions.Synchronized)] - хотя лично я предпочитаю частный объект блокировки (как вы использовали), чтобы предотвратить проблемы с внешними людьми, блокирующими объект (вышеизложенное использует «this» как замок).

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

Если вас интересует обёртка, вы можете рассмотреть существующий код, например this .

1 голос
/ 05 октября 2008

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

this.runningThreads.Set(this.runningThreads.Get() + 1);

Здесь довольно очевидное состояние гонки. Когда возвращается вызов Get(), объект больше не блокируется. Чтобы сделать реальную запись или предварительное увеличение, счетчик должен быть заблокирован от до Get до Set.

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

Лучший интерфейс блокировки (я думаю) потребовал бы от вас явной блокировки экземпляра, в котором вы должны это сделать. Мой опыт в основном связан с C ++, поэтому я не могу рекомендовать полную реализацию, но мой предпочтительный синтаксис может выглядеть примерно так:

using (Locked<T> lock = Locked<T>(instance))
{
  // write value
  instance++;
}

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