Goroutines
A goroutine is a function running concurrently in Go’s scheduler. Launching one costs ~2KB of stack — cheap enough that “thousands of goroutines” is normal. Channels pass data between them.
Goroutines, channels, sync.WaitGroup
EXAMPLE
package main
import (
"fmt"
"sync"
"time"
)
// 1) Launch a goroutine — just `go funcCall(...)`
func main() {
go say("hello")
say("world")
}
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
// 2) WaitGroup — wait for N goroutines to finish
func fanOut(urls []string) {
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
fetch(u)
}(u)
}
wg.Wait()
}
// 3) Channel — typed pipe between goroutines
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i * i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
// 4) Buffered channel — non-blocking up to N
ch := make(chan string, 10)
// 5) Worker pool pattern
func workerPool(jobs <-chan Job, results chan<- Result, n int) {
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := range jobs {
results <- process(j)
}
}(i)
}
wg.Wait()
close(results)
}
// 6) select — wait on multiple channels
select {
case v := <-a: fmt.Println("from a:", v)
case v := <-b: fmt.Println("from b:", v)
case <-time.After(2 * time.Second):
fmt.Println("timeout")
}
// 7) Context — cancellation propagates
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go func() {
select {
case <-ctx.Done():
return // bail out cleanly
case <-doWork():
// ...
}
}()
// 8) Mutex — protect shared state
var (
mu sync.Mutex
cache = map[string]string{}
)
func get(k string) string {
mu.Lock()
defer mu.Unlock()
return cache[k]
}
// 9) sync.Once — initialise exactly once
var (
initOnce sync.Once
client *http.Client
)
func getClient() *http.Client {
initOnce.Do(func() {
client = &http.Client{Timeout: 10 * time.Second}
})
return client
}
// 10) Avoid common bugs
// - Don't share variables across goroutines without sync
// - Always close(channel) on the SENDER side
// - Use errgroup.Group for goroutines that can return errors
Why it matters
Goroutines + channels replace thread pools and callback hell. The mental model is “cheap workers + typed pipes” — everything else (timeouts, cancellation, fan-out, fan-in) composes from those two primitives.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
go func() {
fmt.Println("in goroutine")
}()
time.Sleep(50 * time.Millisecond)
Try it Yourself »
Exercise
Launch a goroutine.
work()
Two letters.
Discussion
Loading…