C# потокобезопасный статический член

У меня есть класс С# со статическим членом, который читается из нескольких потоков и записывается в один поток.

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

У меня было несколько идей, как это сделать.

  1. Сделайте это с атомным классом-оболочкой, например std::atomic в С++. Есть ли что-то подобное на C#?

  2. Используйте модификатор volatile со статическим полем. Однако это не допускается. Почему?

  3. Наконец я сделал следующее:

    private static object tick_time_lock;
    private static UInt64 _currentTickTime;
    public static UInt64 CurrentTickTime 
    {
        get
        {
            return _currentTickTime;
        }
        set
        {
            lock (tick_time_lock)
            {
                _currentTickTime = value;
            }
        }
    }
    

Это правильный способ сделать это поле потокобезопасным?


person VSZM    schedule 25.11.2013    source источник
comment
да, однако помните, что он будет замедляться, если у вас установлено много потоков. также будет состояние гонки. например, если вы обновите значение до... 1, следующий поток может обновить его до 2, но затем 4-й может обновить его до... 4, а 3-й может появиться позже из-за некоторых условий гонки и переопределить последнее актуальное значение. Кстати, вам нужно обновить объект tick_time_lock.   -  person Ahmed ilyas    schedule 26.11.2013
comment
Возможно, вы ищете Interlocked служебный класс.   -  person millimoose    schedule 26.11.2013


Ответы (5)


Это правильный способ сделать это поле потокобезопасным?

Блокировка монитора не имеет смысла, если все обращения к данному ресурсу не синхронизированы. Блокировка аксессора set довольно бесполезна, если вы не заблокируете аксессор get. Как вы сказали, чтение и запись значений UInt64 не являются атомарными на всех платформах. Что произойдет, если поле будет прочитано в методе доступа get, когда в методе доступа set записано только первое слово? Вы получите рваное чтение.

Используйте модификатор volatile со статическим полем. Однако это не допускается. Почему?

Разработчики языка C# сочли выгодным гарантировать атомарность всех volatile доступов к полям. В качестве компромисса вы не можете объявить любое 64-битное поле как volatile. Я не знаю наверняка, почему было принято такое решение. Возможно, они хотели избежать добавления «скрытых» накладных расходов к некоторым непостоянным операциям чтения/записи и вместо этого требовали, чтобы разработчики зависели от средств уровня инфраструктуры, таких как Thread.Volatile[Read/Write](ref long), для обработки 64-битных значений.

Сделайте это с атомным классом-оболочкой, например std::atomic в С++. Есть ли что-то подобное на C#?

да. Через класс System.Threading.Interlocked доступны атомарные операции на уровне фреймворка, включая Read, Exchange, CompareExchange.

person Mike Strobel    schedule 25.11.2013


Для 64-битных целых чисел вы также можете рассмотреть возможность использования членов класса Interlocked в среде .NET:

http://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx

person Will Dean    schedule 25.11.2013

Нет, вы делаете это совершенно неправильно. Блокировка только операций чтения/записи вам не поможет. Например, этот код не является потокобезопасным, даже несмотря на то, что и get, и set могут быть защищены блокировками:

var time = Clock.CurrentTickTime;
time += 1
Clock.CurrentTickTime = time

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

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

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

person Euphoric    schedule 25.11.2013

Я думаю, вам нужно будет создать экземпляр объекта блокировки. Также используйте блокировку для get.

private static Object tick_time_lock = new Object();
private static UInt64 _currentTickTime;
public static UInt64 CurrentTickTime 
{
    get
    {
        lock (tick_time_lock)
        {
            return _currentTickTime;
        }
    }
    set
    {
        lock (tick_time_lock)
        {
            _currentTickTime = value;
        }
    }
}
person Szymon    schedule 25.11.2013