Потоковые безопасные свойства в С#
Я пытаюсь создать потокобезопасные свойства в С#, и я хочу убедиться, что я на правильном пути - вот что я сделал -
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);
}
}
}
}
Я новичок в обеспечении безопасности потока кода, поэтому, пожалуйста, не стесняйтесь говорить мне, если я собираюсь сделать это совершенно неправильно!
Будет
Ответы
Ответ 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();
Это явно не является потокобезопасным, и индивидуально защищая свойства вышеописанным образом не поможет. В этом случае блокировка должна выполняться на другом уровне, а не на уровне каждого уровня.