Описание пакета sync/atomic

Пакет atomic предоставляет низкоуровневые примитивы атомарной памяти, полезные для реализации алгоритмов синхронизации.

Для правильного использования этих функций требуется большая осторожность. За исключением специальных низкоуровневых приложений, синхронизацию лучше выполнять с помощью каналов или средств пакета sync. Делитесь памятью, общаясь; не общайтесь, делясь памятью.

Операция обмена, реализуемая функциями SwapT, является атомарным эквивалентом:

old = *addr
*addr = new
return old

Операция сравнения и замены, реализуемая функциями CompareAndSwapT, является атомарным эквивалентом:

if *addr == old {
	*addr = new
	return true
}
return false

Операция сложения, реализуемая функциями AddT, является атомарным эквивалентом:

*addr += delta
return *addr

Операции load и store, реализуемые функциями LoadT и StoreT, являются атомарными эквивалентами “return *addr” и “*addr = val”.

В терминологии модели памяти Go, если эффект атомарной операции A наблюдается атомарной операцией B, то A “синхронизируется раньше” B. Кроме того, все атомарные операции, выполняемые в программе, ведут себя так, как будто выполняются в некотором последовательно согласованном порядке. Это определение обеспечивает ту же семантику, что и последовательные атомики в C++ и волатильные переменные в Java.

Функции

func AddInt32

func AddInt32(addr *int32, delta int32) (new int32)

AddInt32 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Add.

func AddInt64

func AddInt64(addr *int64, delta int64) (new int64)

AddInt64 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func AddUint32

func AddUint32(addr *uint32, delta uint32) (new uint32)

AddUint32 атомарно добавляет дельту к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint32(&x, ^uint32(c-1)). В частности, чтобы уменьшить x, выполните AddUint32(&x, ^uint32(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Add.

func AddUint64

func AddUint64(addr *uint64, delta uint64) (new uint64)

AddUint64 атомарно добавляет delta к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint64(&x, ^uint64(c-1)). В частности, чтобы уменьшить x, выполните AddUint64(&x, ^uint64(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func AddUintptr

func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)

AddUintptr атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Add.

func AndInt32

func AndInt32(addr *int32, mask int32) (old int32)

AndInt32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.And.

func AndInt64

func AndInt64(addr *int64, mask int64) (old int64)

AndInt64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.And вместо этого.

func AndUint32

func AndUint32(addr *uint32, mask uint32) (old uint32)

AndUint32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.And вместо него.

func AndUint64

func AndUint64(addr *uint64, mask uint64) (old uint64)

AndUint64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.And вместо этого.

func AndUintptr

func AndUintptr(addr *uintptr, mask uintptr) (old uintptr)

AndUintptr атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.And вместо этого.

func CompareAndSwapInt32

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

CompareAndSwapInt32 выполняет операцию сравнения и замены для значения int32. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.CompareAndSwap.

func CompareAndSwapInt64

func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)

CompareAndSwapInt64 выполняет операцию сравнения и замены для значения int64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func CompareAndSwapPointer

func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

CompareAndSwapPointer выполняет операцию сравнения и обмена для значения unsafe.Pointer. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.CompareAndSwap.

func CompareAndSwapUint32

func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)

CompareAndSwapUint32 выполняет операцию сравнения и замены для значения uint32. Рассмотрите возможность использования более удобной и менее подверженной ошибкам функции Uint32.CompareAndSwap.

func CompareAndSwapUint64

func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)

CompareAndSwapUint64 выполняет операцию сравнения и замены для значения uint64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func CompareAndSwapUintptr

func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

CompareAndSwapUintptr выполняет операцию сравнения и замены для значения uintptr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.CompareAndSwap.

func LoadInt32

func LoadInt32(addr *int32) (val int32)

LoadInt32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Load.

func LoadInt64

func LoadInt64(addr *int64) (val int64)

LoadInt64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func LoadPointer

func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

LoadPointer атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Load.

func LoadUint32

func LoadUint32(addr *uint32) (val uint32)

LoadUint32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Load.

func LoadUint64

func LoadUint64(addr *uint64) (val uint64)

LoadUint64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func LoadUintptr

func LoadUintptr(addr *uintptr) (val uintptr)

LoadUintptr атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Load.

func OrInt32

func OrInt32(addr *int32, mask int32) (old int32)

OrInt32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, указанную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Or.

func OrInt64

func OrInt64(addr *int64, mask int64) (old int64)

OrInt64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Or вместо него.

func OrUint32

func OrUint32(addr *uint32, mask uint32) (старый uint32)

OrUint32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Or.

func OrUint64

func OrUint64(addr *uint64, mask uint64) (old uint64)

OrUint64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Or вместо него.

func OrUintptr

func OrUintptr(addr *uintptr, mask uintptr) (old uintptr)

OrUintptr атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Or.

func StoreInt32

func StoreInt32(addr *int32, val int32)

StoreInt32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Store.

func StoreInt64

func StoreInt64(addr *int64, val int64)

StoreInt64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func StorePointer

func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)

StorePointer атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Store.

func StoreUint32

func StoreUint32(addr *uint32, val uint32)

StoreUint32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Store.

func StoreUint64

func StoreUint64(addr *uint64, val uint64)

StoreUint64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func StoreUintptr

func StoreUintptr(addr *uintptr, val uintptr)

StoreUintptr атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Store.

func SwapInt32

func SwapInt32(addr *int32, new int32) (old int32)

SwapInt32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Swap.

func SwapInt64

func SwapInt64(addr *int64, new int64) (old int64)

SwapInt64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func SwapPointer

func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)

SwapPointer атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Swap.

func SwapUint32

func SwapUint32(addr *uint32, new uint32) (old uint32)

SwapUint32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Swap.

func SwapUint64

func SwapUint64(addr *uint64, new uint64) (old uint64)

SwapUint64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func SwapUintptr

func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)

SwapUintptr атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Swap.

Типы

type Bool

type Bool struct {
    // содержит отфильтрованные или неэкспортируемые поля
}

Bool — это атомарное булево значение. Нулевое значение — false.

func (*Bool) CompareAndSwap

func (x *Bool) CompareAndSwap(old, new bool) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для булевого значения x.

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var flag atomic.Bool
	flag.Store(false)

	// Пытаемся изменить false → true (успешно)
	swapped := flag.CompareAndSwap(false, true)
	fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())

	// Пытаемся изменить false → true (неуспешно, текущее значение уже true)
	swapped = flag.CompareAndSwap(false, true)
	fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())
}

func (*Bool) Load

func (x *Bool) Load() bool

Load атомарно загружает и возвращает значение, хранящееся в x.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

func main() {
	var ready atomic.Bool
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(500 * time.Millisecond)
		ready.Store(true)
	}()

	for !ready.Load() {
		fmt.Println("Waiting...")
		time.Sleep(100 * time.Millisecond)
	}
	fmt.Println("Ready!")
	wg.Wait()
}

func (*Bool) Store

func (x *Bool) Store(val bool)

Store атомарно сохраняет val в x.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var initialized atomic.Bool
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			if initialized.Load() {
				fmt.Printf("Worker %d: already initialized\n", id)
				return
			}

			initialized.Store(true)
			fmt.Printf("Worker %d: did initialization\n", id)
		}(i)
	}

	wg.Wait()
}

func (*Bool) Swap

func (x *Bool) Swap(new bool) (old bool)

Swap атомарно сохраняет new в x и возвращает предыдущее значение.

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var toggle atomic.Bool
	toggle.Store(false)

	// Переключаем значение и получаем старое
	old := toggle.Swap(true)
	fmt.Println("Old value:", old, "New value:", toggle.Load())

	// Переключаем ещё раз
	old = toggle.Swap(false)
	fmt.Println("Old value:", old, "New value:", toggle.Load())
}

type Int32/Int64/Uint32/Uint64/Uintptr

type Int32 struct {
    // содержит отфильтрованные или неэкспортируемые поля
}

Int32 — это атомарное целое число int32. Нулевое значение — ноль. Int64 — это атомарное целое число int64. Нулевое значение — ноль. Uint32 - это атомарный uint32. Нулевое значение равно нулю. Uint64 - это атомарный uint64. Нулевое значение равно нулю. Uintptr - это атомарный uintptr. Нулевое значение равно нулю.

func (*Int32) Add

func (x *Int32) Add(delta int32) (new int32)

Add атомарно добавляет delta к x и возвращает новое значение.

func (*Int32) And

func (x *Int32) And(mask int32) (old int32)

And атомарно выполняет побитовую операцию AND над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.

func (*Int32) CompareAndSwap

func (x *Int32) CompareAndSwap(old, new int32) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для x.

func (*Int32) Load

func (x *Int32) Load() int32

Load атомарно загружает и возвращает значение, хранящееся в x.

func (*Int32) Or

func (x *Int32) Or(mask int32) (old int32)

Or атомарно выполняет операцию побитового ИЛИ над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.

func (*Int32) Store

func (x *Int32) Store(val int32)

Store атомарно сохраняет val в x.

func (*Int32) Swap

func (x *Int32) Swap(new int32) (old int32)

Swap атомарно сохраняет new в x и возвращает предыдущее значение.

type Pointer

type Pointer[T any] struct {
    // содержит отфильтрованные или неэкспортируемые поля
}

Pointer — это атомарный указатель типа *T. Нулевое значение — nil *T.

func (*Pointer[T]) CompareAndSwap

func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для x.

func (*Pointer[T]) Load

func (x *Pointer[T]) Load() *T

Load атомарно загружает и возвращает значение, хранящееся в x.

func (*Pointer[T]) Store

func (x *Pointer[T]) Store(val *T)

Store атомарно сохраняет val в x.

func (*Pointer[T]) Swap

func (x *Pointer[T]) Swap(new *T) (old *T)

Swap атомарно сохраняет new в x и возвращает предыдущее значение.

type Value

type Value struct {
    // содержит отфильтрованные или неэкспортируемые поля
}

Value обеспечивает атомарную загрузку и хранение значения с постоянным типом. Нулевое значение для Value возвращает nil из Value.Load. После вызова Value.Store Value не должен копироваться.

Value не должен копироваться после первого использования.

Пример config
package main

import (
	"fmt"
	"math/rand"
	"sync/atomic"
	"time"
)

// ServerConfig содержит конфигурацию сервера
type ServerConfig struct {
	Host    string
	Port    int
	Timeout time.Duration
	Debug   bool
}

var (
	config  atomic.Value // Текущая конфигурация
	counter atomic.Int32 // Счетчик запросов
)

func loadConfig() ServerConfig {
	// Имитация загрузки конфигурации (в реальности может быть из файла, БД и т.д.)
	rand.Seed(time.Now().UnixNano())
	return ServerConfig{
		Host:    fmt.Sprintf("server-%d.example.com", rand.Intn(3)+1),
		Port:    8000 + rand.Intn(100),
		Timeout: time.Duration(rand.Intn(5)+1) * time.Second,
		Debug:   rand.Intn(2) == 0,
	}
}

func processRequest(workerID int) {
	for {
		// Имитация обработки запроса
		time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)

		// Атомарно получаем текущую конфигурацию
		conf := config.Load().(ServerConfig)
		reqID := counter.Add(1)

		fmt.Printf("Worker %d processing request #%d with config: %+v\n",
			workerID, reqID, conf)
	}
}

func configUpdater() {
	for {
		time.Sleep(5 * time.Second)
		newConfig := loadConfig()
		config.Store(newConfig)
		fmt.Printf("\nConfig updated: %+v\n\n", newConfig)
	}
}

func main() {
	// Инициализация начальной конфигурации
	config.Store(loadConfig())
	fmt.Println("Initial config:", config.Load())

	// Запускаем обновление конфигурации в фоне
	go configUpdater()

	// Запускаем воркеров для обработки запросов
	for i := 1; i <= 5; i++ {
		go processRequest(i)
	}

	// Даем поработать 30 секунд
	time.Sleep(30 * time.Second)
	fmt.Println("Server shutting down...")
}

Вывод программы:

Initial config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}

Worker 5 processing request #1 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Worker 3 processing request #2 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}

Config updated: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}

Worker 1 processing request #3 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Worker 4 processing request #4 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Пример ReadMostly
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

// ConcurrentMap реализует потокобезопасную map с помощью atomic.Value
type ConcurrentMap struct {
	data atomic.Value // хранит map[string]string
	mu   sync.Mutex   // только для записи
}

// NewConcurrentMap создает новую потокобезопасную map
func NewConcurrentMap() *ConcurrentMap {
	cm := &ConcurrentMap{}
	cm.data.Store(make(map[string]string))
	return cm
}

// Get безопасно получает значение по ключу
func (cm *ConcurrentMap) Get(key string) (string, bool) {
	currentMap := cm.data.Load().(map[string]string)
	val, ok := currentMap[key]
	return val, ok
}

// Set безопасно устанавливает значение по ключу
func (cm *ConcurrentMap) Set(key, value string) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	// Копируем текущую map
	oldMap := cm.data.Load().(map[string]string)
	newMap := make(map[string]string, len(oldMap)+1)

	// Копируем все элементы
	for k, v := range oldMap {
		newMap[k] = v
	}

	// Добавляем/обновляем элемент
	newMap[key] = value

	// Атомарно заменяем map
	cm.data.Store(newMap)
}

// Delete безопасно удаляет элемент по ключу
func (cm *ConcurrentMap) Delete(key string) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	oldMap := cm.data.Load().(map[string]string)
	if _, exists := oldMap[key]; !exists {
		return
	}

	newMap := make(map[string]string, len(oldMap))
	for k, v := range oldMap {
		if k != key {
			newMap[k] = v
		}
	}

	cm.data.Store(newMap)
}

func main() {
	config := NewConcurrentMap()

	// Записываем начальные значения
	config.Set("host", "localhost")
	config.Set("port", "8080")
	config.Set("timeout", "30s")

	// Запускаем 5 читателей
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 3; j++ {
				time.Sleep(time.Millisecond * 100)
				if val, ok := config.Get("host"); ok {
					fmt.Printf("Reader %d: host=%s\n", id, val)
				}
			}
		}(i)
	}

	// Запускаем 2 писателя
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 2; j++ {
				time.Sleep(time.Millisecond * 200)
				newHost := fmt.Sprintf("server-%d-%d", id, j)
				config.Set("host", newHost)
				fmt.Printf("Writer %d updated host to %s\n", id, newHost)
			}
		}(i)
	}

	// Запускаем удаление через некоторое время
	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(time.Millisecond * 300)
		config.Delete("timeout")
		fmt.Println("Deleted timeout key")
	}()

	wg.Wait()

	// Финальное состояние
	fmt.Println("\nFinal config:")
	if host, ok := config.Get("host"); ok {
		fmt.Println("host:", host)
	}
	if port, ok := config.Get("port"); ok {
		fmt.Println("port:", port)
	}
	if _, ok := config.Get("timeout"); !ok {
		fmt.Println("timeout: (deleted)")
	}
}

Вывод программы:

Reader 4: host=localhost
Reader 0: host=localhost
Reader 2: host=localhost
Reader 1: host=localhost
Writer 0 updated host to server-0-0
Reader 3: host=server-0-0
Reader 0: host=server-0-0
Reader 4: host=server-0-0
Writer 1 updated host to server-1-0
Reader 2: host=server-1-0
Deleted timeout key
Reader 1: host=server-1-0
Writer 0 updated host to server-0-1
Writer 1 updated host to server-1-1
Reader 3: host=server-1-1

Final config:
host: server-1-1
port: 8080
timeout: (deleted)

func (*Value) CompareAndSwap

func (v *Value) CompareAndSwap(old, new any) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для Value.

Все вызовы CompareAndSwap для данного значения должны использовать значения одного и того же конкретного типа. CompareAndSwap несовместимого типа вызывает панику, как и CompareAndSwap(old, nil).

func (*Value) Load

func (v *Value) Load() (val any)

Load возвращает значение, установленное последним Store. Он возвращает nil, если для этого значения не было вызова Store.

func (*Value) Store

func (v *Value) Store(val any)

Store устанавливает значение Value v равным val. Все вызовы Store для данного Value должны использовать значения одного и того же конкретного типа. Store несовместимого типа вызывает панику, как и Store(nil).

func (*Value) Swap

func (v *Value) Swap(new any) (old any)

Swap сохраняет new в Value и возвращает предыдущее значение. Он возвращает nil, если Value пуст.

Все вызовы Swap для данного Value должны использовать значения одного и того же конкретного типа. Swap несовместимого типа вызывает панику, как и Swap(nil).