Async programming in Go
-
Go support async programming using
goroutines (lightweight threads) and
channels. It does not have
async/await
keywords.It passes message between goroutines using channels. The Go runtime scheduler multiplexes goroutines onto thread pool threads. When a goroutine blocks on I/O, another goroutine runs.
See also: Goroutine creation, Channels.
1. Goroutines — fire-and-forget concurrency
-
Prefix any function call with
go to run it concurrently.
main does not wait unless you use a WaitGroup,
channel, or sync primitive.
package main
import (
"fmt"
"time"
)
func fetchUser() {
time.Sleep(100 * time.Millisecond) // simulate network I/O
fmt.Println("user fetched")
}
func main() {
go fetchUser() // runs concurrently — like an async task
fmt.Println("main continues")
time.Sleep(200 * time.Millisecond) // wait so goroutine can finish
}
2. Channels — async message passing
-
Goroutines communicate by sending values on channels. An unbuffered
channel blocks until both sender and receiver are ready — built-in
synchronization.
package main
import "fmt"
func fetch(ch chan string) {
ch <- "result from API" // send when done (blocks until main receives)
}
func main() {
ch := make(chan string)
go fetch(ch) // start async work
result := <-ch // main waits for result — like await
fmt.Println(result)
}
3. select — wait on multiple async operations
select waits on one or more channel operations — similar to
waiting on multiple futures. Use with time.After for
timeouts.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(50 * time.Millisecond)
ch1 <- "service-A"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "service-B"
}()
select {
case msg := <-ch1:
fmt.Println("got", msg)
case msg := <-ch2:
fmt.Println("got", msg)
case <-time.After(100 * time.Millisecond):
fmt.Println("timeout")
}
}
4. sync.WaitGroup — wait for many goroutines
-
When
main must wait for several concurrent tasks to finish
before exiting.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Println("task", id, "done")
}(i)
}
wg.Wait() // block until all goroutines call Done()
fmt.Println("all finished")
}
5. context — cancel async work
context.Context carries deadlines, cancellation signals,
and request-scoped values across goroutines. Pass it into long-running
or I/O functions so they can stop when the caller cancels.
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
select {
case <-time.After(2 * time.Second):
fmt.Println("work finished")
case <-ctx.Done():
fmt.Println("cancelled:", ctx.Err())
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
go worker(ctx)
time.Sleep(1 * time.Second)
}
Summary: Go achieves async-style concurrency with
go + channels + select, not
async/await. For HTTP servers, the standard
library (net/http) handles each request in its own
goroutine automatically.