Конкуренция в Go

Теория: Инструменты и отладка

Конкурентные программы ведут себя иначе, чем последовательные. Ошибки здесь редко проявляются явно: гонки данных случаются только при определенном межпотоковом расписании, блокировки появляются не всегда, а потерянные миллисекунды на ожидании легко спрятаны в общей картине работы. Именно поэтому такой код всегда сопровождают специализированными инструментами.

Go предлагает три ключевых механизма, которые помогают увидеть внутреннее устройство программы: детектор гонок -race, профилировщик pprof и настройку параллелизма через GOMAXPROCS. Каждый из них решает свою задачу: один показывает проблемы согласованности доступа, второй — узкие места по времени и памяти, третий — управление количеством используемых процессорных ядер.

Детектор гонок: go run -race

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

Команда:

go run -race main.go

или

go test -race ./...

запускает программу в режиме слежения за доступом к памяти. Рантайм автоматически отслеживает все чтения и записи и сообщает, если две операции конфликтуют.

Минимальный пример:

var n int
var wg sync.WaitGroup
wg.Add(2)

go func() {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		n++ // гонка
	}
}()

go func() {
	defer wg.Done()
	for i := 0; i < 1000; i++ {
		n++
	}
}()

wg.Wait()
fmt.Println(n)

Запуск с -race покажет стек вызовов, укажет строку и место конфликта.

После исправления — например, через sync.Mutex() — предупреждения исчезнут.

Важно помнить: режим -race замедляет выполнение и увеличивает память в несколько раз. Его используют только в тестах и отладке, а не в продакшене.

Профилировщик pprof

Когда программа работает корректно, но медленно, помогает профилирование. Оно показывает, как распределяется процессорное время, где возникают задержки, сколько памяти выделяет код и насколько часто он блокируется на мьютексах или каналах.

Профиль из тестов

Для коротких программ:

go test -run ^$ -bench . -cpuprofile cpu.out
go tool pprof cpu.out

В отчете будут видны функции, в которых тратится больше всего времени.

Профиль работающего приложения

В сервис pprof включают через HTTP:

import _ "net/http/pprof"
import "net/http"

func main() {
	go http.ListenAndServe(":6060", nil)
	select {}
}

Снятие профиля:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10

Результат — отчет или flame graph (через -http=:8080), где видно, какие функции нагружают CPU, сколько выделено памяти, где программа ждет замок или канал.

Пример функции, потребляющей CPU:

func heavy() float64 {
	var s float64
	for i := 0; i < 10_000_000; i++ {
		s += math.Sqrt(float64(i))
	}
	return s
}

Профиль честно показывает, что почти все время уходит в math.Sqrt, и это не ошибка — просто тяжелая операция.

Параллелизм и GOMAXPROCS

Количество горутин в Go не эквивалентно количеству ядер. Параллельно может выполняться только столько горутин, сколько логических ядер Go разрешено использовать. Это значение задает GOMAXPROCS.

По умолчанию оно равно числу CPU, но его можно изменить:

runtime.GOMAXPROCS(1)

или:

GOMAXPROCS=2 go run main.go

Пример, показывающий влияние параметра:

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	sum := 0
	for i := 0; i < 1e7; i++ {
		sum += i
	}
}

Если GOMAXPROCS равен 1, все воркеры делят одно ядро. При увеличении до 2, 4 и выше выполнение ускоряется — но только до предела количества физических ядер. Дальнейшее увеличение уже не помогает: возрастает только число переключений контекста.

Общая стратегия отладки

Инструменты лучше всего работают в связке. Стандартная последовательность выглядит так:

  1. Проверить корректность. Запустить программу с -race и устранить все гонки данных.
  2. Понять производительность. С помощью pprof измерить, куда уходят CPU, память и время ожидания.
  3. Настроить параллелизм. Подобрать GOMAXPROCS и параметры воркеров так, чтобы эффективно использовать ресурсы системы.

После этих шагов конкурентная программа становится наблюдаемой: ее поведение можно измерить

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845