Потокобезопасные свойства в C # - PullRequest
32 голосов
/ 23 августа 2011

Я пытаюсь создать потокобезопасные свойства в C # и хочу убедиться, что я нахожусь на правильном пути - вот что я сделал -

private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice 
{
    get
    {
        lock (AvgBuyPriceLocker)
        {
            return _AvgBuyPrice;
        }
    }
    set
    {
        lock (AvgBuyPriceLocker)
        {
            _AvgBuyPrice = value;
        }
    }
}

Читая это сообщение, может показаться, что это неправильный способ сделать это -

C # потокобезопасность с get / set

однако, эта статья, кажется, предлагает иное,

http://www.codeproject.com/KB/cs/Synchronized.aspx

У кого-нибудь есть более точный ответ?

Edit:

Причина, по которой я хочу сделать Getter / Setter для этого свойства, заключается в том, что b / c я на самом деле хочу, чтобы оно вызывало событие, когда оно установлено - поэтому код на самом деле был бы таким:

public class PLTracker
{

    public PLEvents Events;

    private readonly object AvgBuyPriceLocker = new object();
    private double _AvgBuyPrice;
    private double AvgBuyPrice 
    {
        get
        {
            lock (AvgBuyPriceLocker)
            {
                return _AvgBuyPrice;
            }
        }
        set
        {
            lock (AvgBuyPriceLocker)
            {
                Events.AvgBuyPriceUpdate(value);
                _AvgBuyPrice = value;
            }
        }
    }
}

public class PLEvents
{
    public delegate void PLUpdateHandler(double Update);
    public event PLUpdateHandler AvgBuyPriceUpdateListener;

    public void AvgBuyPriceUpdate(double AvgBuyPrice)
    {
        lock (this)
        {
            try
            {
                if (AvgBuyPriceUpdateListener!= null)
                {
                    AvgBuyPriceUpdateListener(AvgBuyPrice);
                }
                else
                {
                    throw new Exception("AvgBuyPriceUpdateListener is null");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

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

Будет

Ответы [ 4 ]

26 голосов
/ 23 августа 2011

Замки, как вы их написали, бессмысленны. Например, поток, читающий переменную, будет:

  1. Получить замок.
  2. Считать значение.
  3. Снять замок.
  4. Используйте значение read как-нибудь.

Ничто не мешает другому потоку изменить значение после шага 3. Поскольку доступ к переменным в .NET является атомарным (см. Предостережение ниже), блокировка на самом деле не достигает многого: просто добавление издержек. Контраст с разблокированным примером:

  1. Считать значение.
  2. Используйте значение read как-нибудь.

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

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

  1. Приобретите замок.
  2. Считать значение.
  3. Используйте значение read как-нибудь.
  4. Снять замок.

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

19 голосов
/ 23 августа 2011

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

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

16 голосов
/ 23 августа 2011

Безопасность потоков - это не то, что вы должны добавить к своим переменным, это то, что вы должны добавить к своей «логике».Если вы добавите блокировки ко всем своим переменным, ваш код не обязательно будет потокобезопасным, но он будет чертовски медленным.Чтобы написать поточно-ориентированную программу, посмотрите на свой код и решите, где несколько потоков могут использовать одни и те же данные / объекты.Добавьте блокировки или другие меры безопасности ко всем этим критическим местам.

Например, если предположить следующий бит псевдокода:

void updateAvgBuyPrice()
{
    float oldPrice = AvgBuyPrice;
    float newPrice = oldPrice + <Some other logic here>
    //Some more new price calculation here
    AvgBuyPrice = newPrice;
}

Если этот код вызывается из нескольких потоков одновременноВаша логика блокировки не имеет смысла.Представьте себе поток A, получающий AvgBuyPrice и выполняющий некоторые вычисления.Теперь, прежде чем это будет сделано, поток B также получает AvgBuyPrice и начинает вычисления.Тем временем поток A завершен и назначит новое значение AvgBuyPrice.Однако через несколько секунд он будет перезаписан потоком B (который все еще использовал старое значение), и работа потока A. будет полностью потеряна.

Так как же это исправить?Если бы мы использовали блокировки (что было бы самым уродливым и самым медленным решением, но самым простым, если вы только начинаете с многопоточности), нам нужно поместить всю логику, которая изменяет AvgBuyPrice, в блокировки:

void updateAvgBuyPrice()
{
    lock(AvgBuyPriceLocker)
    {
        float oldPrice = AvgBuyPrice;
        float newPrice = oldPrice + <Some other code here>
        //Some more new price calculation here
        AvgBuyPrice = newPrice;
    }
}

Теперь, если поток B хочет выполнить вычисления, пока поток A все еще занят, он будет ждать, пока поток A не будет завершен, и затем выполнит свою работу, используя новое значение.Имейте в виду, однако, что любой другой код, который также изменяет AvgBuyPrice, должен также блокировать AvgBuyPriceLocker, пока он работает!

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

6 голосов
/ 23 августа 2011

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

private float AvgBuyPrice
{
    get;
    set;
}

Я хочу сказать, что безопасность потоков более сложна, чем просто защита каждого из ваших свойств. В качестве простого примера предположим, что у меня есть два свойства AvgBuyPrice и StringAvgBuyPrice:

private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }

И предположим, что я обновляю среднюю цену покупки таким образом:

this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();

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

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