Опиши основные способы взаимодействия и синхронизации горутин в Go.

Ответ

В Go горутины (легковесные потоки) взаимодействуют и синхронизируются с помощью нескольких ключевых механизмов:

  1. Запуск горутины — создание и запуск новой горутины:

    go func() {
        // Код, выполняемый в новой горутине
        fmt.Println("Привет из горутины!")
    }()
  2. Ожидание завершенияsync.WaitGroup используется для ожидания завершения одной или нескольких горутин:

    var wg sync.WaitGroup
    wg.Add(1) // Увеличиваем счетчик на 1
    go func() {
        defer wg.Done() // Уменьшаем счетчик по завершении горутины
        fmt.Println("Горутина выполняет работу...")
        time.Sleep(time.Second)
    }()
    wg.Wait() // Блокирует выполнение, пока счетчик не станет 0
    fmt.Println("Все горутины завершены.")
  3. Синхронизация и передача данных через каналы — каналы (chan) являются основным способом безопасной передачи данных и синхронизации между горутинами (принцип "Do not communicate by sharing memory; instead, share memory by communicating."):

    ch := make(chan int) // Создаем канал для int
    go func() {
        ch <- 42 // Отправляем значение в канал
    }()
    value := <-ch // Получаем значение из канала (блокирующая операция)
    fmt.Printf("Получено: %dn", value)
  4. Блокировка/разблокировка доступа к общим даннымsync.Mutex (мьютекс) или sync.RWMutex (мьютекс чтения/записи) используются для защиты общих ресурсов от одновременного доступа нескольких горутин:

    var mu sync.Mutex
    var counter int
    
    go func() {
        mu.Lock()   // Захватываем мьютекс
        counter++   // Безопасно изменяем общую переменную
        mu.Unlock() // Освобождаем мьютекс
    }()
  5. Остановка по контекстуcontext.Context используется для передачи дедлайнов, отмены сигналов и значений между API-границами и горутинами:

    ctx, cancel := context.WithCancel(context.Background())
    go func(ctx context.Context) {
        select {
        case <-ctx.Done(): // Ожидаем сигнала отмены
            fmt.Println("Горутина отменена.")
            return
        case <-time.After(5 * time.Second):
            fmt.Println("Горутина завершила работу по таймауту.")
        }
    }(ctx)
    
    time.Sleep(time.Second) // Даем горутине немного поработать
    cancel() // Отправляем сигнал отмены
    time.Sleep(time.Second) // Ждем завершения горутины
  6. Обработка паникиrecover() используется внутри defer для перехвата паники в горутине, предотвращая крах всей программы:

    go func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("Горутина паниковала: %vn", r)
            }
        }()
        panic("Что-то пошло не так!")
    }()
    time.Sleep(time.Second) // Даем время для паники и восстановления

Эти механизмы позволяют эффективно управлять параллелизмом и обеспечивать безопасность данных в Go-приложениях.