Go Concurrency


Go’s concurrency model, built around goroutines and channels, offers a powerful yet elegant way to write efficient and concurrent programs. Unlike traditional threading, goroutines are lightweight, allowing you to launch thousands of them without bogging down your system. Let’s break down how it works.

Goroutines: Lightweight Concurrency

A goroutine is a function executing concurrently with other goroutines in the same address space. It’s incredibly cheap to create and manage, thanks to Go’s runtime scheduler. The keyword go kicks off a new goroutine:

package main

import (
	"fmt"
	"time"
)

func printHello() {
	fmt.Println("Hello from a goroutine!")
}

func main() {
	go printHello() // Launch a new goroutine
	fmt.Println("Main function continues...")
	time.Sleep(100 * time.Millisecond) // Allow the goroutine time to execute.
}

In this example, printHello runs concurrently with the main function. The time.Sleep in main is essential to ensure the program doesn’t exit before the goroutine has a chance to finish.

Channels: Communication and Synchronization

Channels facilitate communication and synchronization between goroutines. They’re typed conduits through which you can send and receive values.

package main

import (
	"fmt"
	"time"
)

func worker(ch chan string) {
	time.Sleep(time.Second)
	ch <- "Work done!"
}

func main() {
	ch := make(chan string) // Create a channel for strings
	go worker(ch)          // Launch a worker goroutine

	message := <-ch // Receive the message from the channel (blocking)
	fmt.Println(message)
}

Here, the main function waits for the worker goroutine to send a message on the channel ch. The <- operator is used both to send and receive values on a channel. The receive operation blocks until a value is available.

Select Statement: Handling Multiple Channels

The select statement enables a goroutine to wait on multiple communication operations. It’s like a switch statement, but for channels.

package main

import (
	"fmt"
	"time"
)

func worker1(ch1 chan string) {
	time.Sleep(2 * time.Second)
	ch1 <- "From worker 1"
}

func worker2(ch2 chan string) {
	time.Sleep(time.Second)
	ch2 <- "From worker 2"
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go worker1(ch1)
	go worker2(ch2)

	select {
	case msg1 := <-ch1:
		fmt.Println(msg1)
	case msg2 := <-ch2:
		fmt.Println(msg2)
	}
}

In this example, the main function waits on both ch1 and ch2. The first channel to send a value triggers its corresponding case within the select block.

These simple examples illustrate the basic building blocks of Go’s concurrency model. This allows for efficient handling of multiple tasks, maximizing CPU utilization and achieving true concurrency.