Назад к вопросам
Middle
100
questionbank

Какие правила следует учитывать при выделении переменной в горутине?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

// Правило 1: Избегайте совместного использования переменных без синхронизации.
// Горутины выполняются параллельно, и доступ к одной и той же переменной
// без блокировок или атомарных операций может привести к состояниям гонки (race conditions).
// Используйте sync.Mutex или каналы для безопасного доступа.

// Пример с race condition:
var counter int
func incrementBad() {
    counter++ // Может быть не потокобезопасным
}

// Пример с Mutex:
import "sync"
var (
    counterSafe int
    mutex sync.Mutex
)
func incrementSafe() {
    mutex.Lock()
    defer mutex.Unlock()
    counterSafe++
}
// Правило 2: Предпочитайте передачу данных между горутинами через каналы.
// Идиома Go: "Не передавайте данные по памяти, а передавайте владение памятью".
// Каналы обеспечивают синхронизацию и упорядочивают доступ к данным.

// Пример с каналом:
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        // Обработка задачи
        results <- j * 2 // Отправка результата
    }
}
// Правило 3: Будьте осторожны с замыканиями, захватывающими переменные из внешней области видимости.
// Горутина может начать выполнение позже, чем завершится цикл или функция, в которой она была создана.
// Если замыкание захватывает переменную по ссылке (что является поведением по умолчанию),
// оно может получить unexpected value. Копируйте значение переменной, если оно нужно внутри горутины.

// Пример с неожиданным поведением замыкания:
func badGoroutineLoop() {
    for i := 0; i < 5; i++ {
        go func() {
            // К моменту запуска всех горутин, i скорее всего будет равно 5
            // fmt.Println(i) // Output: 5 5 5 5 5
        }()
    }
}

// Пример с правильным захватом переменной:
func goodGoroutineLoop() {
    for i := 0; i < 5; i++ {
        iLocal := i // Создаем локальную копию переменной
        go func() {
            // fmt.Println(iLocal) // Output: 0 1 2 3 4 (порядок может варьироваться)
        }()
    }
}
// Правило 4: Используйте sync.WaitGroup для ожидания завершения всех запущенных горутин.
// Это позволяет избежать преждевременного завершения основной горутины (например, main),
// до того, как параллельные задачи будут выполнены.

// Пример с WaitGroup:
import "sync"
var wg sync.WaitGroup
func main() {
    for i := 0; i < 3; i++ {
        wg.Add(1) // Увеличиваем счетчик ожиданий
        go func(id int) {
            defer wg.Done() // Сообщаем о завершении работы горутины
            // Работаем...
        }(i)
    }
    wg.Wait() // Ожидаем, пока счетчик не станет равным 0
    // Все горутины завершены
}