Goroutine Best Practices Guide¶
ЦЕЛЬ ДОКУМЕНТА¶
Этот guide содержит proven практики для безопасного использования goroutines в проекте Saga, основанные на реальном аудите кодовой базы и выявленных safe patterns.
ПРОВЕРЕННЫЕ SAFE PATTERNS¶
1. 🔄 WaitGroup Coordination Pattern (РЕКОМЕНДУЕТСЯ)¶
Использование: Parallel execution с known number of goroutines
// ✅ ПРАВИЛЬНО - используется в tests/performance/load/boundary/test.go
func processParallelOperations(operations []Operation) {
var wg sync.WaitGroup
results := make(chan Result, len(operations))
for i, op := range operations {
wg.Add(1)
go func(index int, operation Operation) {
defer wg.Done() // ОБЯЗАТЕЛЬНО в начале
result := operation.Execute()
results <- result
}(i, op)
}
wg.Wait()
close(results)
}
Ключевые принципы:
- ✅
wg.Add(1)ДО горутины - ✅
defer wg.Done()ПЕРВОЕ в горутине - ✅
wg.Wait()блокирует до завершения всех - ✅ Buffered channel с правильным размером
2. ⏰ Context-Aware Timeout Pattern (PRODUCTION)¶
Использование: Long-running operations с timeout protection
// ✅ ПРАВИЛЬНО - используется в blockchain/service/deposit_service.go
func scanBlockchainHistory(ctx context.Context, fromBlock, toBlock uint64) error {
go func() {
scanCtx, scanCancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer scanCancel() // ОБЯЗАТЕЛЬНО для cleanup
defer func() {
// State cleanup после завершения
updateScanProgress(false, "Сканирование завершено")
}()
err := performActualScan(scanCtx, fromBlock, toBlock)
if err != nil {
logError("scan failed", err)
}
}()
return nil
}
Ключевые принципы:
- ✅
context.WithTimeout()для protection от зависания - ✅
defer cancel()для cleanup context - ✅ Multiple defer blocks для proper state management
- ✅ Error handling внутри горутины
3. 📡 Channel Communication Pattern (TESTS)¶
Использование: Результаты из concurrent operations
// ✅ ПРАВИЛЬНО - используется в tests/integration/error/scenarios/test.go
func concurrentRequests(requestCount int) []Result {
results := make(chan Result, requestCount) // Buffered!
for i := 0; i < requestCount; i++ {
go func(requestIndex int) {
result := performRequest(requestIndex)
results <- result // НЕ блокируется благодаря buffer
}(i)
}
// Собираем результаты
var allResults []Result
for i := 0; i < requestCount; i++ {
allResults = append(allResults, <-results)
}
return allResults
}
Ключевые принципы:
- ✅ Buffered channel размером = количеству горутин
- ✅ Finite execution - горутины завершаются естественно
- ✅ Результаты всегда читаются из канала
4. 🛡️ Defensive I/O Pattern (DATABASE)¶
Использование: Защита от зависания I/O операций
// ✅ ПРАВИЛЬНО - используется в shared/testing/real_database_manager.go
func closeWithTimeout(resource io.Closer) error {
done := make(chan error, 1) // Buffered для non-blocking
go func() {
done <- resource.Close() // Может зависнуть
}()
select {
case err := <-done:
return err
case <-time.After(10 * time.Second):
return fmt.Errorf("resource close timeout")
}
}
Ключевые принципы:
- ✅ Buffered channel size 1 для non-blocking
- ✅
select+time.After()для timeout - ✅ Горутина завершается независимо от timeout
5. 🏥 Health Check Pattern (SERVICES)¶
Использование: Parallel health checks в production
// ✅ ПРАВИЛЬНО - используется в shared/services/unified/health_service.go
func runHealthChecks(checkers []HealthChecker) []HealthResult {
var wg sync.WaitGroup
results := make(chan HealthResult, len(checkers))
for _, checker := range checkers {
wg.Add(1)
go func(c HealthChecker) {
defer wg.Done()
results <- c.Check(ctx) // Context с timeout
}(checker)
}
// Closer горутина
go func() {
wg.Wait()
close(results) // Закрываем только после всех
}()
// Собираем результаты
var allResults []HealthResult
for result := range results { // range останавливается при close
allResults = append(allResults, result)
}
return allResults
}
Ключевые принципы:
- ✅ Separate "closer" горутина для channel management
- ✅ Channel закрывается ТОЛЬКО после
wg.Wait() - ✅
rangeавтоматически stops при close channel
ОПАСНЫЕ ANTI-PATTERNS (ИЗБЕГАТЬ)¶
1. 🚫 Infinite Loop Without Context¶
// ❌ ОПАСНО - может leak навсегда
go func() {
for {
doWork() // НЕТ способа остановить
time.Sleep(time.Second)
}
}()
❌ Проблемы:
- Нет способа остановить горутину
- Memory leak при shutdown
- Blocked resources
✅ ИСПРАВЛЕНИЕ:
go func() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return // Graceful shutdown
case <-ticker.C:
doWork()
}
}
}()
2. 🚫 Blocking Channel Operations¶
// ❌ ОПАСНО - может hang навсегда
ch := make(chan Data) // Unbuffered!
go func() {
ch <- data // Блокируется если нет receiver
}()
❌ Проблемы:
- Горутина может зависнуть навсегда
- Sender блокируется без receiver
✅ ИСПРАВЛЕНИЕ:
ch := make(chan Data, 1) // Buffered
go func() {
select {
case ch <- data:
// Success
case <-time.After(5 * time.Second):
// Timeout handling
}
}()
3. 🚫 Resource Leaks¶
// ❌ ОПАСНО - resource не закрывается
go func() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
return // LEAK! conn не закрыт
}
// Работа с connection
// НЕТ cleanup при panic или error
}()
❌ Проблемы:
- Connection leak при early return
- Нет cleanup при panic
- Resource exhaustion
✅ ИСПРАВЛЕНИЕ:
go func() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
return
}
defer conn.Close() // ОБЯЗАТЕЛЬНО cleanup
// Безопасная работа с connection
}()
CHECKLIST ДЛЯ CODE REVIEW¶
Перед созданием новой горутины проверь:¶
Lifecycle Management:¶
- Termination strategy - как горутина завершается?
- Context cancellation - можно ли остановить извне?
- Timeout protection - есть ли защита от зависания?
- Resource cleanup - освобождаются ли ресурсы?
Communication:¶
- Channel sizing - buffered channels правильного размера?
- Channel closure - кто и когда закрывает каналы?
- Synchronization - используется ли WaitGroup для coordination?
- Error handling - как передаются ошибки?
Testing:¶
- Test isolation - не влияют ли горутины на другие тесты?
- Deterministic behavior - предсказуемы ли результаты?
- Cleanup in tests - есть ли teardown для горутин?
Monitoring:¶
- Logging - логируются ли start/stop events?
- Metrics - отслеживается ли количество горутин?
- Debug information - достаточно ли данных для troubleshooting?
🏭 PRODUCTION MONITORING¶
Встроенная система мониторинга¶
В cmd/saga/main.go реализована автоматическая система мониторинга:
// Production-ready мониторинг в main.go
func startGoroutineMonitoring(cfg config.ConfigInterface) {
threshold := 1000 // Настраиваемый порог
interval := 30 * time.Second
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
count := runtime.NumGoroutine()
// Regular monitoring
sagaServerLogger.InfoStructured("Goroutine monitoring",
"count", count, "threshold", threshold)
// Alert на превышение
if count > threshold {
sagaServerLogger.WarnStructured("High goroutine count detected",
"count", count, "threshold", threshold)
}
}
}()
}
Интерпретация метрик¶
🟢 Нормальные значения:
- Startup: 5-20 горутин
- Idle: 10-50 горутин
- Load: 50-500 горутин
⚠️ Предупреждающие значения:
- 500-1000: Высокая нагрузка, мониторить
- 1000+: Возможный leak, требует investigation
🚨 Критические значения:
- 5000+: Серьезный leak, immediate action required
- Growing trend: Постоянный рост = guaranteed leak
🛠️ DEBUGGING TOOLS¶
Runtime diagnostics¶
# Проверить количество горутин в runtime
go tool pprof http://localhost:6060/debug/pprof/goroutine
# Stack traces всех горутин
curl http://localhost:6060/debug/pprof/goroutine?debug=1
# Goroutine blocking profile
go tool pprof http://localhost:6060/debug/pprof/block
Code analysis¶
# Найти все горутины в коде
grep -rn 'go func(' ./backend/ --include="*.go"
# Потенциально опасные patterns
grep -rn 'go func()' ./backend/ --include="*.go" # Без параметров
grep -rn 'for.*{' ./backend/ -A 10 | grep 'go func' # В циклах
ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ¶
Go Documentation¶
📖 Best Practices¶
Static Analysis Tools¶
go vet- встроенный analyzergolangci-lint- comprehensive lintingdeadcode- dead code detection
🏆 ЗАКЛЮЧЕНИЕ¶
Команда Saga демонстрирует excellent knowledge Go concurrency patterns. Все найденные горутины в production коде используют safe practices и не представляют риска memory leaks.
Ключевые принципы успеха:
- Consistent patterns - команда использует проверенные решения
- Context awareness - timeout protection где нужно
- Resource cleanup - proper defer blocks
- Test isolation - горутины в тестах безопасны
Следуй этому guide для всех новых горутин!
Связанные документы¶
- Backend Architecture: Backend Architecture - понимание архитектуры где используются эти паттерны
- Testing Guide: Testing Documentation - тестирование concurrent кода
- Performance Monitoring: Monitoring System - production мониторинг горутин
- Go 2025 Patterns: CLAUDE.md раздел "⚡ GO 2025 МОДЕРНИЗАЦИЯ" - современные практики Go включая concurrency
Документ основан на real audit данных проекта Saga, проведенном 2025-09-22 Все примеры кода взяты из актуальной кодовой базы и проверены на production readiness
📋 Метаданные¶
Версия: 2.4.82
Обновлено: 2025-10-21
Статус: Published