Практические паттерны и оптимизация в 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. Освоение этих паттернов позволит создавать высокопроизводительные и безопасные многопоточные приложения.
Галерея
Полезные статьи:
Новые статьи: