Skip to the content.

Async in Golang

Goroutines implement asynchronous execution through Go’s runtime scheduler, which is designed to manage and execute goroutines efficiently across available CPU cores. Here’s a detailed explanation of how this works:


Key Mechanisms Enabling Asynchronous Execution in Goroutines

1. Goroutine Abstraction

2. Go Runtime Scheduler

3. Non-blocking Execution

4. Preemption

5. Channels and Synchronization


How Asynchronous Execution Works in Practice

Example 1: Concurrent Execution

package main

import (
	"fmt"
	"time"
)

func work(id int) {
	time.Sleep(2 * time.Second) // Simulate work
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	for i := 1; i <= 3; i++ {
		go work(i) // Launch work asynchronously
	}

	fmt.Println("Main function continues...")
	time.Sleep(3 * time.Second) // Wait to see all outputs
}

Output:

Main function continues...
Worker 1 done
Worker 2 done
Worker 3 done

Example 2: Non-blocking I/O

package main

import (
	"fmt"
	"net/http"
)

func fetchURL(url string) {
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("Fetched %s: %d\n", url, resp.StatusCode)
}

func main() {
	go fetchURL("https://example.com")
	go fetchURL("https://golang.org")

	// Prevent main from exiting immediately
	select {}
}

Explanation:


Behind the Scenes: Why Is It Async?

  1. Dynamic Stack Management:
    • Each goroutine starts with a small stack, making it cheaper to create and run multiple goroutines. The stack grows or shrinks based on need, avoiding large memory overhead.
  2. Efficient Context Switching:
    • The runtime scheduler switches between goroutines efficiently, without the overhead of kernel-level context switches.
  3. I/O Multiplexing:
    • For I/O operations, Go leverages OS-level mechanisms like epoll (Linux), kqueue (macOS/BSD), or IOCP (Windows). This allows a single thread to manage multiple I/O operations asynchronously.
  4. Runtime Cooperation:
    • The scheduler ensures that goroutines cooperate, yielding control when they perform blocking operations like channel communication or time.Sleep.

In summary, goroutines achieve asynchronous execution by being lightweight, non-blocking, and efficiently managed by the Go runtime scheduler, which multiplexes them across OS threads. This design enables Go programs to perform high-concurrency operations with simplicity and minimal overhead.