Ответ
Состояние гонки (Race Condition) — это ошибка проектирования многопоточной системы, при которой результат работы зависит от непредсказуемого порядка выполнения операций в разных горутинах.
Проблема возникает, когда две или более горутины одновременно обращаются к одной и той же области памяти (общему ресурсу), и хотя бы одна из них выполняет запись.
Пример
Классический пример — инкремент счетчика из нескольких горутин без синхронизации. Операция counter++ неатомарна и состоит из трех шагов: чтение, изменение, запись. Другая горутина может прочитать значение до того, как первая его запишет, что приведет к потере инкремента.
var counter int
func increment() {
counter++ // Небезопасная операция
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println(counter) // Результат будет < 1000
}
Способы предотвращения в Go
-
Мьютексы (
sync.Mutex): Самый распространенный способ. Мьютекс позволяет только одной горутине получить доступ к критической секции кода.var mu sync.Mutex mu.Lock() counter++ mu.Unlock() -
Каналы: Использование каналов для передачи данных между горутинами, что позволяет избежать общего доступа к памяти. Например, одна горутина может владеть данными и обрабатывать запросы на их изменение, получаемые из канала.
-
Атомарные операции (
sync/atomic): Для простых операций, таких как инкремент, декремент илиCompareAndSwap, пакетatomicпредоставляет аппаратно-оптимизированные функции, которые выполняются без прерываний. Это эффективнее, чем использование мьютекса.atomic.AddInt64(&counter, 1)
Обнаружение
Go имеет встроенный детектор гонок. Чтобы его использовать, нужно запустить программу с флагом -race:
go run -race main.go Ответ 18+ 🔞
А, слушай, вот тебе про гонки, блядь. Это не когда ты на тачке с мужиками на светофоре соревнуешься, а когда у тебя в программе горутины начинают друг другу мозги ебать, пытаясь к одной и той же переменной одновременно добраться.
Суть в чём, ёпта: представь, у тебя есть общая тарелка с печеньками — это твоя память. И есть два обдолбанных программиста — это горутины. Оба хотят взять печеньку и при этом сказать всем, сколько печенек осталось. Если они полезют одновременно, один возьмёт, посчитает, что осталось девять, а второй, не глядя, тоже возьмёт и тоже напишет «девять». А на деле-то уже восемь, блядь! Вот это и есть состояние гонки — результат зависит от того, кто какого хуя раньше руку протянул, и это непредсказуемо.
Особенно пиздец наступает, когда кто-то не просто читает, а ещё и пишет. Чистое чтение — ещё ладно, а вот запись — это уже война.
Наглядный пиздец
Смотри, классический пример — счётчик. Кажется, что counter++ — это одна операция, а на деле там три: прочитать значение, увеличить его на один, записать обратно. И вот пока одна горутина между «прочитать» и «записать» мечтает о прекрасном, вторая уже успевает вклиниться и прочитать старое значение. В итоге оба увеличат одно и то же число, и инкремент потеряется, как твои носки в стиралке.
var counter int // Наша общая печенька, блядь
func increment() {
counter++ // О, наивный! Думаешь, это атомарно? Хуй там!
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment() // Запускаем тысячу обдолбанных программистов к одной печеньке
}()
}
wg.Wait()
fmt.Println(counter) // И тут мы охуеваем, потому что результат будет меньше 1000. Где-то 956, где-то 998... Пиздец, а не гонка.
}
Как не дать им друг друга переебать
-
Мьютексы (
sync.Mutex) — это как табличка «Занято» на сортире. Одна горутина вешает, делает свои дела, потом снимает. Пока висит — все остальные ждут, как лохи.var mu sync.Mutex // Наш верный замок, блядь mu.Lock() // Вешаем табличку counter++ // Спокойно жрём печеньку, никто не мешает mu.Unlock() // Сняли, пусть следующий идёт -
Каналы — это уже философия, ёпта. Вместо того чтобы всем лезть к одной печеньке, ты заводишь одного ответственного бармена. Все желающие пишут ему записки в канал: «Дай печеньку» или «Доложи печенек». А бармен один всё обрабатывает. Никаких общих данных — одна горутина владеет всем, остальные общаются через каналы. Умно, но иногда овердохуища кода.
-
Атомарные операции (
sync/atomic) — это когда тебе настолько похуй на изящество, что ты просто берёшь и делаешь операцию за один такт процессора, без возможности прерваться. Для счётчиков — самое то. Быстрее, чем мьютекс, но только для простых чисел и булов.atomic.AddInt64(&counter, 1) // Раз — и готово. Никаких тебе трёх шагов, всё за один удар.
Как это всё отловить, пока не стало поздно
А вот тут Go — молодец, ёбаный в рот. В нём прямо в коробке лежит детектор гонок. Запускаешь программу с флагом -race, и он тебе подсвечивает все подозрительные места, где горутины могут друг другу ебальника набросить.
go run -race main.go
Выдаст тебе красивый отчёт, где, кто и на какой строке пытался конкурировать. Пользуйся, не будь мудаком, проверяй свой код. А то потом ночью будешь сидеть и думать: «И почему же у меня баланс пользователей постоянно на три рубля схуячивается?».