Goroutines Preview
JavaScript’s concurrency model: async/await
Section titled “JavaScript’s concurrency model: async/await”JavaScript is single-threaded. Concurrency comes from the event loop: when an async operation (network, I/O) completes, a callback is scheduled. async/await is syntactic sugar over Promises — it makes asynchronous code read like synchronous code, but there is still only one thread executing user JavaScript at a time.
Go takes a completely different approach: goroutines are genuinely concurrent lightweight threads, and the Go runtime schedules them across multiple OS threads. You can run millions of goroutines on a typical machine.
Launching a goroutine
Section titled “Launching a goroutine”The go keyword launches a function in a new goroutine — a lightweight, independently scheduled execution context. It returns immediately; the goroutine runs concurrently.
// TypeScript — async/await (event loop, single thread)async function fetchUser(id: number): Promise<User> { const res = await fetch(`/users/${id}`); return res.json();}
async function main() { // These run sequentially by default: const u1 = await fetchUser(1); const u2 = await fetchUser(2);
// To run concurrently, use Promise.all: const [u1, u2] = await Promise.all([fetchUser(1), fetchUser(2)]);}// Go — goroutines (OS-thread-backed, truly parallel)func fetchUser(id int) User { // ... actual network call return User{ID: id}}
func main() { // go keyword — launches fetchUser concurrently go fetchUser(1) // fire and forget (result lost) go fetchUser(2)
// To collect results, use channels or sync.WaitGroup}sync.WaitGroup — wait for goroutines to finish
Section titled “sync.WaitGroup — wait for goroutines to finish”sync.WaitGroup is the simplest way to wait for a known number of goroutines to complete before the main goroutine exits. Think of it as a counter that blocks until it reaches zero.
// TypeScript — Promise.all to wait for allconst results = await Promise.all( [1, 2, 3].map(id => fetchUser(id)));console.log(results);func processItem(id int, wg *sync.WaitGroup) { defer wg.Done() // decrement when done fmt.Printf("processing %d\n", id)}
func main() { var wg sync.WaitGroup
for i := 1; i <= 3; i++ { wg.Add(1) // increment counter go processItem(i, &wg) // launch goroutine }
wg.Wait() // block until counter == 0 fmt.Println("all done")}Channels — communicate between goroutines
Section titled “Channels — communicate between goroutines”A channel is a typed conduit for sending values between goroutines. You send a value with <- and receive a value with <-. Channels synchronize the sender and receiver — sending blocks until someone receives, and vice versa (for unbuffered channels).
// TypeScript — no native channel primitive// Closest approximation: a queue + event emitter, or a streamimport { Readable } from "stream";// ... complex plumbing requiredfunc producer(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i // send i into channel } close(ch) // signal no more values}
func main() { ch := make(chan int) // unbuffered channel
go producer(ch) // runs concurrently
for v := range ch { // receive until channel closed fmt.Println("received:", v) }}Try it
Section titled “Try it”package main
import ( "fmt" "sync")
func fetchData(id int, wg *sync.WaitGroup, results chan<- string) { defer wg.Done() // Simulate work result := fmt.Sprintf("result from worker %d", id) results <- result}
func main() { const numWorkers = 3 var wg sync.WaitGroup results := make(chan string, numWorkers) // buffered channel
for i := 1; i <= numWorkers; i++ { wg.Add(1) go fetchData(i, &wg, results) }
// Close channel when all goroutines finish go func() { wg.Wait() close(results) }()
// Collect results for r := range results { fmt.Println(r) } fmt.Println("all workers done")}Loading Go runtime (first run only, ~8 MB)…