Goroutines

Introduction

Goroutines are a fundamental feature of Go, allowing you to run functions concurrently with other goroutines. They are lightweight, managed by the Go runtime, and are essential for building concurrent programs. In this chapter, you will learn the basics of creating and managing goroutines, along with best practices and common patterns.

Creating a Goroutine

A goroutine is created by prefixing a function call with the go keyword. This function will run concurrently with the calling function.

Example: Basic 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.

Anonymous Goroutines

You can also start a goroutine using an anonymous function.

Example: Anonymous Goroutine

Example:

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        fmt.Println("Hello from anonymous goroutine!")
    }()

    time.Sleep(1 * time.Second) // Give the goroutine time to run
}

In this example, an anonymous function is run concurrently with the main function.

Synchronizing Goroutines

To coordinate the execution of goroutines, Go provides several synchronization primitives, such as channels and the sync package.

Example: Using Channels

Channels allow goroutines to communicate with each other and synchronize their execution.

Example:

package main

import (
    "fmt"
)

func sayHello(done chan bool) {
    fmt.Println("Hello, World!")
    done <- true // Signal that the function is done
}

func main() {
    done := make(chan bool)
    go sayHello(done)
    <-done // Wait for the goroutine to finish
}

In this example, a channel is used to signal when the sayHello function is done.

Example: Using WaitGroup

The sync.WaitGroup is another way to wait for multiple goroutines to finish.

Example:

package main

import (
    "fmt"
    "sync"
)

func sayHello(wg *sync.WaitGroup) {
    defer wg.Done() // Mark the goroutine as done
    fmt.Println("Hello, World!")
}

func main() {
    var wg sync.WaitGroup

    wg.Add(1) // Add a goroutine to the wait group
    go sayHello(&wg)

    wg.Wait() // Wait for all goroutines to finish
}

In this example, a WaitGroup is used to wait for the sayHello function to finish.

Example: Spawning Multiple Goroutines

You can start multiple goroutines and synchronize their execution using channels or WaitGroup.

Example: Multiple Goroutines with Channels

Example:

package main

import (
    "fmt"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, j)
        results <- j * 2
    }
}

func main() {
    const numWorkers = 3
    const numJobs = 5

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    for w := 1; w <= numWorkers; w++ {
        go worker(w, jobs, results)
    }

    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    for a := 1; a <= numJobs; a++ {
        fmt.Println("Result:", <-results)
    }
}

Example: Multiple Goroutines with WaitGroup

Example:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d started\n", id)
    // Simulate work
    fmt.Printf("Worker %d finished\n", id)
}

func main() {
    var wg sync.WaitGroup

    const numWorkers = 3

    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, &wg)
    }

    wg.Wait()
    fmt.Println("All workers finished")
}

Best Practices

  1. Avoid Global Variables: Minimize the use of global variables to reduce the risk of race conditions.
  2. Use Channels and Synchronization Primitives: Use channels and synchronization primitives like sync.WaitGroup and sync.Mutex to coordinate goroutines.
  3. Limit Goroutine Creation: Avoid creating too many goroutines, as this can lead to resource exhaustion.
  4. Handle Panics: Use recover within goroutines to handle panics and prevent unexpected crashes.
  5. Monitor Goroutine Lifecycle: Be aware of the lifecycle of your goroutines to avoid leaks and ensure they complete as expected.

Conclusion

Goroutines are a powerful feature of Go that enable concurrent execution of functions. By understanding how to create, synchronize, and manage goroutines, you can write efficient and scalable concurrent programs. Proper use of channels and synchronization primitives is essential to coordinate the execution of goroutines and ensure that your programs run smoothly.

Leave a Comment

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

Scroll to Top