Практические паттерны и оптимизация в Go vs Java | Concurrency часть 3

В этой части мы рассмотрим практические паттерны параллельной обработки задач: worker pool, pipeline pattern и схемы сборки результатов. Эти паттерны помогают повысить производительность и избегать deadlocks.

1. Worker Pool

Worker pool позволяет ограничить количество одновременно работающих goroutine, что эффективно для контроля ресурсов.


// Go: Worker Pool
jobs := make(chan int, 10)
results := make(chan int, 10)

for w := 1; w <= 3; w++ {
    go func(id int) {
        for j := range jobs {
            results <- j*2
        }
    }(w)
}

// Отправляем задачи
for j := 1; j <= 5; j++ {
    jobs <- j
}
close(jobs)

// Получаем результаты
for a := 1; a <= 5; a++ {
    fmt.Println(<-results)
}

// Java: Worker Pool
ExecutorService executor = Executors.newFixedThreadPool(3);

try {
    List<Future<Integer>> futures = new ArrayList<>();

    for (int j = 1; j <= 5; j++) {
        int task = j;
        futures.add(executor.submit(() -> task * 2));
    }

    for (Future<Integer> f : futures) {
        System.out.println(f.get());
    }

} finally {
    executor.shutdown();
}
Worker pool полезен, когда нужно ограничить количество одновременно выполняемых задач и контролировать нагрузку на систему.

2. Pipeline Pattern

Pipeline pattern — организация последовательной обработки данных через цепочку каналов (stage-by-stage). Позволяет разделять задачи на этапы и эффективно использовать goroutine.


// Go: Pipeline
source := make(chan int, 5)
stage1 := make(chan int, 5)
stage2 := make(chan int, 5)

// Stage 1
go func() {
    for n := range source {
        stage1 <- n * 2
    }
    close(stage1)
}()

// Stage 2
go func() {
    for n := range stage1 {
        stage2 <- n + 1
    }
    close(stage2)
}()

// Отправка данных
for i := 1; i <= 5; i++ {
    source <- i
}
close(source)

// Получение результатов
for r := range stage2 {
    fmt.Println(r)
}

// Java: Pipeline с CompletableFuture
List<CompletableFuture<Integer>> pipeline = new ArrayList<>();

for (int i = 1; i <= 5; i++) {
    pipeline.add(
        CompletableFuture.supplyAsync(() -> i)
            .thenApply(n -> n * 2)
            .thenApply(n -> n + 1)
    );
}

for (CompletableFuture<Integer> f : pipeline) {
    System.out.println(f.get());
}
Pipeline помогает разделять задачи на этапы и снижает вероятность блокировок. Особенно полезно для потоковой обработки данных.

3. Мини‑схема: «Параллельная обработка задач + сбор результатов»

Визуально паттерны worker pool + fan-in можно показать так:

        jobs
         │
         ▼
   ┌───────────┐
   │  Worker 1 │
   └───────────┘
         │
   ┌───────────┐
   │  Worker 2 │
   └───────────┘
         │
   ┌───────────┐
   │  Worker 3 │
   └───────────┘
         │
         ▼
      results

4. Лучшие практики

  • Ограничивайте количество одновременно работающих goroutine (worker pool).
  • Используйте buffered каналы для избежания блокировки при fan-in/fan-out.
  • Всегда закрывайте каналы, когда они больше не нужны.
  • Мониторьте гонки и deadlocks с помощью go run -race.
  • Разделяйте задачи на стадии через pipeline для читаемости и безопасности.
Планирование worker pool и pipeline помогает безопасно масштабировать обработку задач и повышает производительность без риска deadlocks.

Итог

В этой статье мы рассмотрели практические паттерны параллельной обработки задач в Go: worker pool, pipeline pattern и сбор результатов через fan-in. Эти паттерны помогают эффективно использовать ресурсы, избегать блокировок и упрощают масштабирование.

Для Java-разработчика это похоже на использование фиксированных пулов потоков, CompletableFuture и последовательной обработки через stage-by-stage. Освоение этих паттернов позволит создавать высокопроизводительные и безопасные многопоточные приложения.


Всего лайков:0
Мой канал в социальных сетях
Отправляя email, вы принимаете условия политики конфиденциальности

Полезные статьи:

От микросервисной революции к эпохе эффективности
Период 2010–2020 годов можно назвать эпохой разделения и масштабирования. Системы стали слишком большими, чтобы оставаться монолитами. Решением стали микросервисы — маленькие автономные приложения, ра...
Generics, Reflection и каналы - Go vs Java | Types - Language
В этой статье мы разберем продвинутые возможности системы типов в Go: generics (параметры типов), reflection (рефлексию) и типы каналов для работы с конкурентностью. Мы сравним подходы Go и Java, чтоб...
Go vs Java - Сравниваем модели памяти - часть 2: atomic operations, preemption, defer/finally, context, escape analysis, GC, false sharing
Atomic operations Atomic операции обеспечивают корректное выполнение операций с переменными без гонок, гарантируя happens-before между чтением и записью. Go пример: import "sync/atomic" var counter in...

Новые статьи:

Resource cleanup, rate‑limiting strategies, bounded vs unbounded channels - в Go vs Java | Паттерны, идиомы и лучшие практики Go
Продолжаем серию статей для разработчиков, которые хотят изучить Go на основе знаний Java, и наоборот. В этой статье мы обсудим три ключевые темы: Resource Cleanup (освобождение ресурсов), Rate-Limiti...
Разбираем: Rate‑limiter, non‑blocking operations, scheduler  Go vs Java | Concurrency часть 4
Эта статья посвящена пониманию принципов работы с конкурентностью и синхронизацией в Go и Java. Мы рассмотрим ключевые подходы, такие как rate‑limiter, неблокирующие операции и планирование задач, сра...
Разбираем: Trace, Profiling, Integration Testing, Code Coverage, Mocking, Deadlock Detection в Go vs Java | Testing, Debugging и Profiling
Серия: Go для Java-разработчиков — разбор trace, профилирования и тестирования В этой статье мы разберем инструменты и практики для тестирования, отладки и профилирования в Go. Для Java-разработчика ...
Fullscreen image