Подтвердить что ты не робот

Потоковые безопасные свойства в С#

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

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

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

Безопасность потока С# с get/set

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

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

Кто-нибудь имеет более окончательный ответ?

Редактировать:

Причина, по которой я хочу сделать 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);
            }
        }
    }
}

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

Будет

4b9b3361

Ответ 1

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

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

Ответ 2

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

  • Приобретите замок.
  • Прочитайте значение.
  • Отпустите блокировку.
  • Используйте значение чтения как-то.

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

  • Прочитайте значение.
  • Используйте значение чтения как-то.

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

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

  • Приобретите замок.
  • Прочитайте значение.
  • Используйте значение чтения как-то.
  • Отпустите блокировку.

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

Ответ 3

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

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

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

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

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

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, пока он работает!

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

Ответ 4

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

private float AvgBuyPrice
{
    get;
    set;
}

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

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

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

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

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