Коротко: нельзя надёжно читать произвольный 16‑байтовый struct «атомарно» просто так — CLR не гарантирует атомарность чтения/записи struct размером > указателя/long. Но есть простые шаблоны, дающие безопасное безблокировочное чтение:
1) Самый простой и рекомендуемый — хранить данные как immutable-ссылку и атомически публиковать новую ссылку.
2) Если вы обязаны хранить struct, — использовать seqlock (versioned read) — читатель читает версию до/после чтения данных и повторяет, пока версия не совпадёт.
3) Ещё вариант — держать всю таблицу как immutable-массив и атомически менять ссылку на массив.
Ниже примеры на C#.
1) Immutable object (рекомендуется)
- Сделайте Note классом с readonly-полями (или record/immutable struct как class-эквивалент).
- При обновлении создавайте новый объект и делайте Interlocked.Exchange/Volatile.Write для публикации.
- Читатели просто читают ссылку — чтение ссылок атомарно и с правильно оформленной семантикой памяти безопасно.
Пример:
```csharp
class Note {
public readonly int A;
public readonly int B;
public Note(int a, int b) { A = a; B = b; }
}
class Cache {
// массив ссылок на immutable Note
private readonly Note[] _notes;
public Cache(int size) { _notes = new Note[size]; }
public void Update(int idx, Note newNote) {
// атомически заменим ссылку
System.Threading.Interlocked.Exchange(ref _notes[idx], newNote);
}
public Note Read(int idx) {
// безопасно читать ссылку; Volatile.Read можно использовать для явного барьера
return System.Threading.Volatile.Read(ref _notes[idx]);
}
}
```
Плюсы: очень просто, читатели супербыстрые (обычное чтение ссылки). Минусы: требуется аллокация нового объекта при каждом обновлении (обычно не проблема, если записи редки).
2) Seqlock (версионирование) — для struct размером 16 байт
Идея: рядом с данными держим целочисленный счётчик версии. Писатель:
- увеличивает версию (делает её нечётной) — это пометка «в процессе записи»,
- записывает данные,
- снова увеличивает версию (делает чётной) — «запись завершена».
Читатель:
- читает версию s1,
- если s1 нечётное — повторить,
- копирует данные (может быть torn),
- читает версию s2,
- если s1 == s2 (и чётное) — данные консистентны; иначе — повторить.
Пример:
```csharp
using System.Threading;
struct Note { public long X; public long Y; } // пример 16 байт
class Cell {
private long _seq; // version counter
private Note _value;
public void Write(Note newVal) {
Interlocked.Increment(ref _seq); // теперь _seq нечётный (write in progress)
// гарантируем порядок: запись данных после увеличения seq
Volatile.Write(ref _value, newVal);
Interlocked.Increment(ref _seq); // теперь чётный (write finished)
}
public Note Read() {
for (;;) {
long s1 = Volatile.Read(ref _seq);
if ((s1 & 1) != 0) continue; // писатель вовремя
Note copy = _value; // тут может быть torn, но тогда seq изменится
long s2 = Volatile.Read(ref _seq);
if (s1 == s2) return copy;
}
}
}
```
Важно:
- Используйте Volatile.Read/Write или MemoryBarrier, чтобы обеспечить нужный порядок операций.
- Seqlock — очень быстрый для читателей, но читатель может зацикливаться, если писатель интенсивно переписывает (у вас писатель редкий — подходит).
- Seqlock даёт «wait-free» чтение (в смысле без блокировок), но не дает гарантии обхода starvation при постоянных писателях.
3) Replace whole array
Если можно — делайте всю таблицу immutable и заменяйте ссылку на массив целиком:
- создаёте новый массив с копией и новым значением,
- публикуете новый массив через Interlocked.Exchange(ref cacheArray, newArray).
Читатели читают ссылку на массив и обращаются без синхронизации. Это удобно, если массив небольшой и записи редки.
Заключение и рекомендации
- Если вы можете — используйте immutable-ссылку (вариант 1) — проще и безопаснее. Чтение максимально быстрое.
- Если хотите избегать аллокаций и обязаны хранить struct — используйте seqlock (вариант 2), но аккуратно с памятью/барьерами.
- Всегда учитывайте архитектуру (32/64 bit) и используйте Volatile/Interlocked для корректных барьеров и атомарных операций.
Если скажете, на каком языке/платформе вы работаете и как выглядит Note (struct или class, поля), дам точный минимальный пример под вашу ситуацию.