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

Важность конкурентности

Благодаря конкурентности, задачи в программах выполняются одновременно, повышаются эффективность и респонсивность. Возьмем веб-сервер, которым обрабатываются тысячи запросов в секунду, или конвейер данных с потоковой обработкой в реальном времени. Традиционная многопоточность сложна, подвержена ошибкам, ресурсоемка. На Go же она упрощается примитивами многопоточности.

Что такое «горутины»?

Горутины  —  это легковесные потоки, управляемые средой выполнения Go. Они эффективнее потоков операционной системы: используют небольшой объем памяти  —  около 2 Кб  —  и при необходимости динамически расширяются. Поэтому одновременно могут запускаться тысячи и даже миллионы горутин.

Как создать горутину

Для этого перед вызовом функции ставится ключевое слово go:

package main

import (
"fmt"
"time"
)

func sayHello() {
fmt.Println("Hello, world!")
}

func main() {
go sayHello() // Запуск горутины
time.Sleep(time.Second) // Ожидание завершения горутины
}

В этом примере функция sayHello() выполняется одновременно с main(). Благодаря time.Sleep до завершения горутины выход из программы не выполняется.

Что такое «каналы»?

Каналы  —  это механизм взаимодействия, безопасной передачи данных между горутинами на Go без явной блокировки.

Как использовать каналы

Канал создается при помощи функции make:

package main

import "fmt"

func main() {
messages := make(chan string)

go func() {
messages <- "Hello from Goroutine!" // Отправка данных
}()

msg := <-messages // Получение данных
fmt.Println(msg)
}

Реальный пример: worker pool

Worker pool  —  это популярный шаблон конкурентного выполнения для эффективной обработки задач. Вот его реализация с горутинами и каналами:

package main

import (
"fmt"
"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second) // Моделирование работы
results <- job * 2
}
}

func main() {
const numJobs = 5
const numWorkers = 3

jobs := make(chan int, numJobs)
results := make(chan int, numJobs)

// Запускаются рабочие потоки — «воркеры»
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}

// Отправляются задания
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)

// Собираются результаты
for a := 1; a <= numJobs; a++ {
fmt.Printf("Result: %d\n", <-results)
}
}

Принцип работы

  • Горутинами worker обрабатываются задачи из канала jobs, результаты отправляются на канал results.
  • Воркеры выполняются одновременно, чем значительно сокращается время выполнения всех задач.

Рекомендации по конкурентному выполнению на Go

  1. Не усложняйте: логику конкурентного выполнения. Простых шаблонов вроде worker pool обычно достаточно.
  2. Ограничьте горутины: применяйте ограниченное их количество во избежание исчерпания ресурсов.
  3. Избегайте блокировки: минимизируйте операции блокировки. Например, по возможности отдавайте предпочтение буферизованным каналам.
  4. Используйте sync.WaitGroup для более аккуратного управления горутинами.

Применение

Конкурентное выполнение применяется при:

  • выполнении задач с интенсивным вводом-выводом: выборка API, считывание файлов и другие;
  • обработке приложением данных в реальном времени или потоковой передаче;
  • распараллеливании вычислений, в итоге время выполнения задач серьезно сокращается.

Заключение

Горутины и каналы  —  это основа, благодаря которой в конкурентное выполнение на Go привносятся простота и мощь. Освоив эти концепции, вы создадите масштабируемые, эффективные и надежные приложения для реальных сценариев.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Muhammad Al Ichsan Nur Rizqi Said: Concurrency in Go: Understanding Goroutines and Channels for Scalable Applications

Предыдущая статья12 библиотек для прокачки фронтенд-разработки
Следующая статьяКомпонентный подход: преодоление сложности в Android-приложениях. Часть 1