Threads → Goroutines
Concurrency
Introduction
In this lesson, you'll learn about threads → goroutines in Go. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Java, you're familiar with concurrency.
Go has its own approach to concurrency, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import "sync"
// Goroutine — just 'go func()'
go fetchData() // fire and forget
// Channel to receive result
ch := make(chan string)
go func() {
ch <- fetchData()
}()
result := <-ch // blocks until value
// Multiple goroutines with WaitGroup
var wg sync.WaitGroup
results := make([]string, 5)
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
results[i] = fetch(i)
}(i)
}
wg.Wait()Comparing to Java
Here's how you might have written similar code in Java:
import java.util.concurrent.*;
// Thread pool
ExecutorService executor = Executors.newFixedThreadPool(4);
// Submit tasks
Future<String> future = executor.submit(() -> {
return fetchData();
});
// Wait for result
String result = future.get(); // blocks
// CompletableFuture for async
CompletableFuture<String> cf =
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(s -> "Result: " + s);
executor.shutdown();You may be used to different syntax or behavior.
Go goroutines are much lighter than Java threads (KB vs MB stack)
You may be used to different syntax or behavior.
Channels replace Java's Future/CompletableFuture for passing results
You may be used to different syntax or behavior.
sync.WaitGroup replaces executor.awaitTermination or CompletableFuture.allOf
You may be used to different syntax or behavior.
Go runtime schedules goroutines; Java uses OS threads via thread pools
Step-by-Step Breakdown
1. go vs Thread Pool
The 'go' keyword starts a goroutine. Unlike Java where you manage thread pools, the Go runtime schedules goroutines automatically.
executor.submit(() -> doWork());go doWork()2. Channel vs Future
Go channels pass values between goroutines — replacing Java's Future.get() and CompletableFuture patterns.
Future<String> f = executor.submit(...); String r = f.get();ch := make(chan string)
go func() { ch <- fetchData() }()
result := <-ch3. WaitGroup
sync.WaitGroup waits for a group of goroutines — like calling join() on all threads or CompletableFuture.allOf().
CompletableFuture.allOf(futures).join();var wg sync.WaitGroup
wg.Add(n); // for each goroutine: go func() { defer wg.Done(); ... }()
wg.Wait()Common Mistakes
When coming from Java, developers often make these mistakes:
- Go goroutines are much lighter than Java threads (KB vs MB stack)
- Channels replace Java's Future/CompletableFuture for passing results
- sync.WaitGroup replaces executor.awaitTermination or CompletableFuture.allOf
Key Takeaways
- go keyword starts lightweight goroutines
- Channels replace Future/CompletableFuture
- WaitGroup replaces thread joins or allOf
- Go runtime manages scheduling; no thread pool needed