Сервис хранит значение, которое читают разные горутины. Какой механизм вызывает панику при выполнении этой программы?
package main
import "sync/atomic"
func main() {
var value atomic.Value
value.Store(int(10))
value.Store(int64(10))
}
Паника возникает потому, что atomic.Value после первой успешной записи фиксирует конкретный динамический тип значения. Первая запись сохраняет значение типа int, а вторая пытается записать значение другого типа — int64.
Все последующие значения должны иметь тот же конкретный тип. Нужно выбрать один тип для всех записей либо хранить значения в общей структуре одного типа.
atomic.Value появился как высокоуровневый примитив для безопасного чтения и замены общего значения без явной блокировки каждого читателя. Типичный исходный сценарий — публикация конфигурации или другого редко изменяемого состояния, которое многие горутины читают одновременно.
Примитив скрывает низкоуровневые атомарные операции и предоставляет единый объект для публикации целостного значения. При этом он специально ограничивает набор допустимых значений, чтобы читатели не наблюдали несовместимые представления данных.
Если разные горутины записывают в atomic.Value значения разных конкретных типов, программа завершается паникой во время второй несовместимой записи. Это может проявиться только на определённой ветви обновления конфигурации и поэтому быть незаметным при обычном тестировании.
Важно отличать конкретный тип от интерфейса, в который значение может быть помещено. Значения int и int64 имеют разные конкретные типы, даже если оба могут использоваться как значения интерфейсного типа.
После первой записи Store запоминает конкретный тип. В примере это int. При второй записи atomic.Value проверяет тип и обнаруживает int64, поэтому выполняется паника о несовместимом типе.
Безопасный вариант — всегда использовать один тип:
Если состояние состоит из нескольких полей, обычно создают структуру и всегда сохраняют значение этой структуры. Это также позволяет публиковать согласованный снимок состояния, а не обновлять его поля по отдельности.
Нулевое значение atomic.Value можно использовать, но до первой записи Load возвращает nil. После первой записи нельзя использовать Store(nil), а тип всех последующих ненулевых значений должен совпадать с типом первой записи.
Сам объект atomic.Value нельзя копировать после начала использования. Его следует создать один раз и передавать между горутинами по указателю либо обращаться к одному экземпляру, например через поле долгоживущего объекта.
atomic.Value не делает произвольный объект автоматически безопасным для изменения. Если внутри хранится указатель на структуру, безопасна атомарная замена самого указателя, но параллельная запись в объект, на который он указывает, требует отдельной синхронизации.
Сервис публикует обновлённую конфигурацию для обработчиков запросов. Один разработчик сохраняет начальную конфигурацию как Config, а при ошибке загрузки пытается записать nil или значение другого типа, полагая, что интерфейс скроет различие.
Вариант с map[string]any позволяет помещать разные типы, но усложняет проверку содержимого и не защищает внутренние данные карты от конкурентной модификации. Вариант с мьютексом проще, если конфигурация изменяется по отдельным полям, но чтения получают блокировку и могут наблюдать состояние только при корректном владении мьютексом.
Предпочтительное решение — неизменяемая структура Config, которую полностью собирают до публикации и целиком сохраняют в atomic.Value. Все записи используют Config одного типа, а читатели получают согласованный снимок без блокировки; обновление становится простой заменой версии конфигурации.
Вопрос: Совместимы ли записи Config{} и *Config в один atomic.Value?
Ответ: Нет. Config и *Config — разные конкретные типы. Нужно выбрать один вариант и использовать его постоянно, например всегда хранить *Config.
Вопрос: Можно ли исправить пример, обернув значения в any перед вызовом Store?
Ответ: Нет. atomic.Value.Store принимает any, но проверяет динамический конкретный тип значения внутри интерфейса. Обёртка int и обёртка int64 всё равно содержат разные динамические типы и будут несовместимы.
Вопрос: Защищает ли atomic.Value поля структуры от конкурентной записи после её публикации?
Ответ: Нет. Он атомарно публикует значение, хранящееся внутри самого Value, но не синхронизирует последующие изменения объекта. Для безопасной модели структуру после публикации не изменяют, а создают новую и целиком публикуют; альтернативой будет отдельный мьютекс.