GoLang – 9 – Concurrency

Concurrency in Go: Goroutines and Concurrency Basics, Synchronization with Channels, WaitGroups, and Mutex

Concurrency is a fundamental feature of Go, enabling developers to write efficient, multi-threaded programs. In this section, we’ll explore the core concepts of concurrency in Go, including goroutines, channels, and synchronization mechanisms like WaitGroups and Mutexes.

Goroutines and Concurrency Basics

Goroutines are lightweight threads managed by the Go runtime. They allow you to execute functions concurrently, making it easy to perform tasks in parallel.

Creating Goroutines

Creating a goroutine is simple. You use the go keyword followed by a function call. For example:


func main() {
    go printNumbers()
    go printLetters()
}

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("%d ", i)
    }
}

func printLetters() {
    for i := 'a'; i <= 'e'; i++ {
        fmt.Printf("%c ", i)
    }
}

In this example, two goroutines, printNumbers and printLetters, run concurrently, printing numbers and letters simultaneously.

Synchronization with Channels

Channels are a powerful mechanism for sharing data and synchronizing goroutines. They allow safe communication between concurrent parts of your program.

Creating Channels

You can create channels with the make function:


ch := make(chan int)

This code creates a channel that can be used to send and receive integer values.

Sending and Receiving Data

You can send data into a channel using the <- operator and receive data from a channel the same way:


ch <- 42
result := <-ch

Here, we send the integer 42 into the channel and then receive it into the result variable.

Blocking with Channels

Channels provide synchronization, which means that sending to or receiving from a channel will block the program until both the sender and receiver are ready. This feature ensures that data is communicated safely between goroutines.


ch := make(chan int)

go func() {
    ch <- 42
}()

result := <-ch

In this example, the main goroutine blocks on the channel until the value 42 is sent by the anonymous goroutine, ensuring synchronization.

WaitGroups and Mutex

Go provides additional synchronization mechanisms like WaitGroups and Mutexes to manage and control concurrent execution.

WaitGroups

WaitGroups are used to wait for a collection of goroutines to finish. They ensure that all goroutines complete their work before the program exits.


var wg sync.WaitGroup

func main() {
    wg.Add(2)

    go func() {
        defer wg.Done()
        printNumbers()
    }()

    go func() {
        defer wg.Done()
        printLetters()
    }()

    wg.Wait()
}

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("%d ", i)
    }
}

func printLetters() {
    for i := 'a'; i <= 'e'; i++ {
        fmt.Printf("%c ", i)
    }
}

The sync.WaitGroup ensures that both printNumbers and printLetters complete before the main function exits.

Mutex

Mutexes are used to protect shared resources in concurrent programs. They prevent multiple goroutines from accessing the same data simultaneously, avoiding data races.


var mu sync.Mutex
var sharedData int

func main() {
    wg.Add(2)

    go incrementSharedData()
    go incrementSharedData()

    wg.Wait()
}

func incrementSharedData() {
    mu.Lock()
    sharedData++
    mu.Unlock()
    wg.Done()
}

The sync.Mutex ensures that the shared data is protected from concurrent modification. When one goroutine acquires the lock, the other waits until it’s released.

Understanding goroutines, channels, and synchronization mechanisms like WaitGroups and Mutexes is essential for effective concurrent programming in Go. These tools enable you to write efficient, parallelized code that fully leverages the capabilities of modern multi-core processors.