Go Concurrency

Introduction

Concurrency is one of the core features of Go, allowing you to write programs that can perform multiple tasks simultaneously. Go’s concurrency model is based on goroutines and channels, which provide a simple yet powerful way to manage concurrent execution. In this chapter, you will learn the basics of Go’s concurrency model, including goroutines, channels, and synchronization techniques.

Goroutines

A goroutine is a lightweight thread managed by the Go runtime. Goroutines allow you to run functions concurrently with other goroutines.

Example: Creating a Goroutine

Example:

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello, World!")
}

func main() {
    go sayHello() // Start a new goroutine
    time.Sleep(1 * time.Second) // Give the goroutine time to run
}

In this example, the sayHello function runs concurrently with the main function.

Channels

Channels provide a way for goroutines to communicate with each other and synchronize their execution. A channel is a conduit through which you can send and receive values of a specific type.

Creating and Using Channels

Example:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string)

    go func() {
        ch <- "Hello, World!" // Send a value to the channel
    }()

    msg := <-ch // Receive a value from the channel
    fmt.Println(msg)
}

In this example, a goroutine sends a message to the ch channel, and the main function receives and prints the message.

Buffered Channels

Buffered channels allow you to specify the capacity of the channel. They block only when the buffer is full or empty.

Example:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string, 2)

    ch <- "Hello"
    ch <- "World"

    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

In this example, the channel has a buffer size of 2, allowing two values to be sent to the channel without blocking.

Channel Synchronization

Channels can also be used to synchronize the execution of goroutines.

Example:

package main

import (
    "fmt"
)

func worker(done chan bool) {
    fmt.Println("Working...")
    done <- true // Signal that the work is done
}

func main() {
    done := make(chan bool)

    go worker(done)

    <-done // Wait for the worker to finish
    fmt.Println("Done")
}

In this example, the main function waits for the worker goroutine to finish by receiving a value from the done channel.

Select Statement

The select statement allows a goroutine to wait on multiple communication operations. It blocks until one of its cases can proceed, then it executes that case.

Example:

package main

import (
    "fmt"
    "time"
)

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

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "One"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "Two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received from ch1:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received from ch2:", msg2)
        }
    }
}

In this example, the select statement waits for messages from either ch1 or ch2 channels and prints the received message.

Mutexes

The sync package provides mutual exclusion locks, or mutexes, to protect shared data from being accessed concurrently by multiple goroutines.

Example:

package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    counter++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go increment(&wg)
    }

    wg.Wait()
    fmt.Println("Counter:", counter) // Output: Counter: 1000
}

In this example, a mutex is used to ensure that only one goroutine increments the counter at a time.

Conclusion

Concurrency in Go is made simple and effective through goroutines and channels. By understanding how to create and manage goroutines, communicate between them using channels, and synchronize their execution using mutexes and the select statement, you can write robust and efficient concurrent programs. This powerful concurrency model is one of the key features that makes Go an excellent choice for building scalable and high-performance applications.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top