потокобезопасность примитивного одновременного чтения и записи - PullRequest
5 голосов
/ 08 января 2012
  • Упрощенная иллюстрация ниже, как .NET справляется с такой ситуацией?
  • и если это вызовет проблемы, придется ли мне блокировать / открывать доступ к каждому полю / свойству, которые могутвремя от времени записывается в + доступ из разных потоков?

Поле где-то

public class CrossRoads(){
    public int _timeouts;
}

Средство записи фонового потока

public void TimeIsUp(CrossRoads crossRoads){
    crossRoads._timeouts++;
}

Возможно одновременно, пытаясь прочитать в другом месте

public void HowManyTimeOuts(CrossRoads crossRoads){
    int timeOuts = crossRoads._timeouts;
}

Ответы [ 5 ]

12 голосов
/ 08 января 2012

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

.NET Framework предоставляет два решения: блокировка и синхронизация потоков.

Для простых манипуляций с типом данных (например, целых) блокировка с использованием Interlocked class будет работать правильно и является рекомендуемым подходом.

На самом деле, блокировка предоставляет специальные методы (Увеличение и Уменьшение), которые облегчают этот процесс:

Добавьте метод IncrementCount в свой класс CrossRoads:

public void IncrementCount() {
    Interlocked.Increment(ref _timeouts);
}

Тогда позвоните своему фоновому работнику:

public void TimeIsUp(CrossRoads crossRoads){
    crossRoads.IncrementCount();
}

Чтение значения, кроме 64-разрядного значения в 32-разрядной ОС, является атомарным. Подробнее см. Документацию по методу Interlocked.Read .

Для объектов класса или более сложных операций вам потребуется использовать поток блокировку синхронизации (блокировка в C # или SyncLock в VB.Net).

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

    private static object SynchronizationObject = new Object();

    public void PerformSomeCriticalWork()
    {
        lock (SynchronizationObject)
        {
            // do some critical work
        }
    }
4 голосов
/ 08 января 2012

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

Я бы использовал:

Interlocked.Increment(ref crossroads._timeouts);

Для записи, которая гарантирует, что значения не будут потеряны, и;

int timeouts = Interlocked.CompareExchange(ref crossroads._timeouts, 0, 0);

Для чтения, поскольку при этом соблюдаются те же правила, что и приращения.Строго говоря, «volatile», вероятно, достаточно для чтения, но он настолько плохо понят, что Interlocked кажется (IMO) более безопасным.В любом случае, мы избегаем блокировки.

3 голосов
/ 08 января 2012

Забудьте дотнет. На уровне машинного языка crossRoads._timeouts++ будет реализовано как инструкция INC [memory]. Это известно как инструкция Read-Modify-Write. Эти инструкции являются атомарными в отношении многопоточности на одном процессоре * (по существу, реализованы с использованием временной синхронизации), но не являются атомарными в отношении многопоточности с использованием нескольких процессоров или нескольких ядер.

Итак:

Если вы можете гарантировать, что только TimeIsUp() когда-либо изменит crossRoads._timeouts, и если вы можете гарантировать, что только один поток когда-либо выполнит TimeIsUp(), то это будет безопасно сделать. Запись в TimeIsUp() будет работать нормально, а чтение в HowManyTimeOuts() (и в любом другом месте) будет работать нормально. Но если вы также измените crossRoads._timeouts в другом месте, или если вы когда-нибудь породили еще одного автора фоновых потоков, у вас будут проблемы.

В любом случае, мой совет - быть осторожным и заблокировать его.

(*) Они являются атомарными по отношению к многопоточности на одном процессоре, поскольку переключение контекста между потоками происходит при периодическом прерывании, а на архитектурах x86 эти инструкции являются атомарными по отношению к прерываниям, что означает, что если происходит прерывание пока процессор выполняет такую ​​инструкцию, прерывание будет ждать, пока инструкция не завершится. Это не относится к более сложным инструкциям, например, с префиксом REP.

3 голосов
/ 08 января 2012

Ну, я не разработчик C #, но на этом уровне это обычно работает:

как .NET справляется с такой ситуацией?

разблокирована. Маловероятно, что он будет атомарным.

Должен ли я заблокировать / обеспечить доступ к каждому полю / свойству, которое иногда может быть записано в + доступ из разных потоков?

Да. Альтернативой было бы сделать блокировку для объекта доступной для клиентов, а затем сказать клиентам, что они должны заблокировать объект при использовании экземпляра. Это уменьшит количество приобретений блокировок и гарантирует более согласованное, предсказуемое состояние для ваших клиентов.

2 голосов
/ 08 января 2012

Хотя int может иметь «родной» размер для ЦП (обрабатывая 32 или 64 бита за раз), если вы читаете и пишете из разных потоков в одну и ту же переменную, лучше всего заблокировать эту переменнуюи синхронизация доступа.

Никогда не существует гарантии , которая читает / записывает, может быть, атомарную int.

. Вы также можете использовать Interlocked.Increment для своих целей здесь.

...