C# Является ли присвоение типа значения атомарным?
Считается ли присвоение типа значения атомарным в.Net?
Например, рассмотрим следующую программу:
struct Vector3
{
public float X { get; private set; }
public float Y { get; private set; }
public float Z { get; private set; }
public Vector3(float x, float y, float z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public Vector3 Clone()
{
return new Vector3(X, Y, Z);
}
public override String ToString()
{
return "(" + X + "," + Y + "," + Z + ")";
}
}
class Program
{
private static Vector3 pos = new Vector3(0,0,0);
private static void ReaderThread()
{
for (int i = 0; i < int.MaxValue; i++)
{
Vector3 v = pos;
Console.WriteLine(v.ToString());
Thread.Sleep(200);
}
}
private static void WriterThread()
{
for (int i = 1; i < int.MaxValue; i++)
{
pos = new Vector3(i, i, i);
Thread.Sleep(200);
}
}
static void Main(string[] args)
{
Thread w = new Thread(WriterThread);
Thread r = new Thread(ReaderThread);
w.Start();
r.Start();
}
}
Может ли такая программа страдать от гонки данных высокого уровня? Или даже гонка данных?
Здесь я хочу знать: есть ли вероятность, что v будет содержать:
- Значения мусора из-за возможной гонки данных
- Смешанные компоненты X, Y или Z, которые относятся как к положению до назначения, так и к положению после назначения. Например, если pos = (1,1,1) и затем назначено pos, новое значение (2,2,2) может v = (1,2,2)?
1 ответ
Структуры являются типами значений. Если вы присвоите структуру переменной / полю / параметру метода, все содержимое структуры будет скопировано из исходного хранилища в место хранения параметра переменной / поля / метода (место хранения в каждом случае равно размеру сама структура).
Копирование структуры не гарантируется как атомарная операция. Как написано в спецификации языка C#:
Атомарность переменных ссылок
Чтение и запись следующих типов данных являются атомарными: типы bool, char, byte, sbyte, short, ushort, uint, int, float и reference. Кроме того, чтение и запись перечислимых типов с базовым типом в предыдущем списке также являются атомарными. Чтение и запись других типов, включая long, ulong, double и decimal, а также определяемые пользователем типы, не гарантированно являются атомарными. Помимо библиотечных функций, разработанных для этой цели, нет гарантии атомарного чтения-изменения-записи, например, в случае увеличения или уменьшения.
Так что да, может случиться так, что в то время как один поток находится в процессе копирования данных из хранилища структур, появляется другой поток и начинает копировать новые данные из другой структуры в это хранилище. Таким образом, поток, копируемый из места хранения, может в итоге скопировать смесь старых и новых данных.
В качестве примечания, ваш код может также страдать от других проблем параллелизма из-за того, как один из ваших потоков пишет в переменную и как переменная используется другим потоком. (Ответ пользователя acelent на другой вопрос объясняет это довольно хорошо с технической точки зрения, поэтому я просто буду ссылаться на него: /questions/30059966/c-volatile-peremennaya-pamyat-ograzhdeniya-vs-keshirovanie/30059968#30059968) Вы можете избежать таких проблем, инкапсулируя любой доступ к таким переменные "пересечения потока" в lock
блок. В качестве альтернативы lock
и что касается основных типов данных, вы также можете использовать методы, предоставляемые Interlocked
класс для доступа к переменным / полям, пересекающим потоки, безопасным для потока способом (чередование между lock
а также Interlocked
хотя методы для одной и той же переменной пересечения потока не очень хорошая идея).