← Назад к вопросам

Что такое CAS?

2.0 Middle🔥 61 комментариев
#Конкурентность и горутины#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

CAS (Compare-And-Swap) в Go

CAS (Compare-And-Swap) — это фундаментальная атомарная операция, используемая в многозадачных и многопоточных системах для реализации синхронизации без блокировок. Она позволяет безопасно изменять значение в памяти, гарантируя, что изменение произойдет только если текущее значение совпадает с ожидаемым.

Механизм работы CAS

Операция CAS выполняет три действия атомарно (неразрывно):

  1. Считывает текущее значение из памяти.
  2. Сравнивает его с ожидаемым значением (expected).
  3. Если значения совпадают, записывает новое значение (new) в эту же память.
  4. Возвращает true при успехе или false при неудаче.

В Go эта операция реализована через функции atomic пакета sync/atomic. Для целочисленных типов используется CompareAndSwapInt32, CompareAndSwapInt64, etc., а для указателей — CompareAndSwapPointer.

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var value int32 = 100
	expected := int32(100)
	newValue := int32(200)

	// Попытка атомарной замены
	swapped := atomic.CompareAndSwapInt32(&value, expected, newValue)
	fmt.Printf("Swapped: %v, New value: %d\n", swapped, value)
	
	// Повторная попытка с другим expected
	expected = 150
	swapped = atomic.CompareAndSwapInt32(&value, expected, newValue)
	fmt.Printf("Swapped: %v, Value unchanged: %d\n", swapped, value)
}

Применение CAS в Go

  1. Реализация спинлоков (Spinlocks):
type SpinLock struct {
	locked int32
}

func (sl *SpinLock) Lock() {
	for !atomic.CompareAndSwapInt32(&sl.locked, 0, 1) {
		// Ждём в цикле (spin)
	}
}

func (sl *SpinLock) Unlock() {
	atomic.CompareAndSwapInt32(&sl.locked, 1, 0)
}
  1. Безблокирующие структуры данных:
    • Пример счетчика с CAS:
type Counter struct {
	value int64
}

func (c *Counter) Increment() {
	for {
		current := atomic.LoadInt64(&c.value)
		if atomic.CompareAndSwapInt64(&c.value, current, current+1) {
			break
		}
	}
}

CAS vs Мьютексы

КритерийCAS (Atomic)Мьютексы (sync.Mutex)
СложностьВысокая (нужно избегать ABA проблем)Средняя
БлокировкиНеблокирующий подходБлокирующий
ПроизводительностьВысокая при низкой конкуренцииЗатраты на блокировки/ожидание
ПрименимостьПростые атомарные операцииКомплексные операции

Проблемы и ограничения CAS

  1. ABA проблема:

    • Между чтением expected и попыткой CAS значение может измениться дважды: A → B → A.
    • CAS успешно выполнится, но состояние системы может быть неожиданным.
    • В Go чаще встречается при работе с указателями.
  2. Спин-ожидание:

    • При высокой конкуренции циклы CAS могут тратить CPU время.
  3. Сложность реализации сложных операций:

    • Для структур, требующих изменения нескольких полей, CAS не подходит напрямую.

CAS в стандартных структурах Go

Пакет sync использует CAS внутри:

  • sync.WaitGroup: атомарные операции над счетчиком.
  • sync.Map: оптимизации через atomic.Value.
  • sync.Pool: управление локальными и общими пулами.

Пример: атомарный указатель

import "sync/atomic"

type Config struct {
	Data string
}

var configPtr atomic.Value // atomic.Value использует CAS внутри

func UpdateConfig(newConfig *Config) {
	configPtr.Store(newConfig)
}

func GetConfig() *Config {
	return configPtr.Load().(*Config)
}

Когда использовать CAS в Go

  • Счетчики и флаги: простые атомарные изменения.
  • Алгоритмы без блокировок: когда важна производительность.
  • Синхронизация инфраструктуры: низкоуровневые конструкции.

CAS — ключевой инструмент для написания высокопроизводительного параллельного кода в Go, но требует осторожности и понимания внутренних механизмов атомарных операций. В большинстве случаев стандартные мьютексы и каналы безопаснее, но для узких мест CAS может дать значительный прирост скорости.

Что такое CAS? | PrepBro