Goroutines are a fundamental concept in Go that allow you to run functions concurrently. They are lightweight threads managed by the Go runtime, making it easy to perform multiple tasks simultaneously without the overhead of traditional threads.

Key Concepts

  1. Concurrency vs. Parallelism:

    • Concurrency: Structuring a program to handle multiple tasks at once.
    • Parallelism: Executing multiple tasks simultaneously, typically on multiple processors.
  2. Goroutines:

    • Lightweight, managed by the Go runtime.
    • Created using the go keyword.
    • Can run concurrently with other goroutines.
  3. Channels:

    • Used for communication between goroutines.
    • Allow goroutines to synchronize and share data.

Creating a Goroutine

To create a goroutine, you simply prefix a function call with the go keyword. Here’s a basic example:

package main

import (
    "fmt"
    "time"
)

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

func main() {
    go sayHello() // This starts a new goroutine
    time.Sleep(1 * time.Second) // Wait for the goroutine to finish
}

Explanation

  • go sayHello(): This starts a new goroutine that runs the sayHello function.
  • time.Sleep(1 * time.Second): This ensures the main function waits long enough for the goroutine to complete. Without this, the program might exit before the goroutine has a chance to run.

Practical Example: Concurrent Tasks

Let's create a more practical example where we perform two tasks concurrently.

package main

import (
    "fmt"
    "time"
)

func task1() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("Task 1 - Count: %d\n", i)
        time.Sleep(500 * time.Millisecond)
    }
}

func task2() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("Task 2 - Count: %d\n", i)
        time.Sleep(700 * time.Millisecond)
    }
}

func main() {
    go task1()
    go task2()
    time.Sleep(4 * time.Second) // Wait for both goroutines to finish
}

Explanation

  • task1 and task2 are two functions that print a count with different delays.
  • Both functions are started as goroutines using the go keyword.
  • time.Sleep(4 * time.Second) ensures the main function waits long enough for both goroutines to complete.

Common Mistakes

  1. Forgetting to Wait: If the main function exits before the goroutines complete, the program will terminate prematurely.

    • Solution: Use time.Sleep or synchronization mechanisms like channels or sync.WaitGroup.
  2. Race Conditions: When multiple goroutines access shared resources without proper synchronization.

    • Solution: Use channels or other synchronization primitives to manage access to shared resources.

Exercise

Task

Create a program that starts three goroutines. Each goroutine should print a message and its ID (1, 2, or 3) five times, with a delay of 300 milliseconds between each print. Ensure the main function waits for all goroutines to complete.

Solution

package main

import (
    "fmt"
    "sync"
    "time"
)

func printMessage(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 1; i <= 5; i++ {
        fmt.Printf("Goroutine %d - Message %d\n", id, i)
        time.Sleep(300 * time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go printMessage(i, &wg)
    }

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

Explanation

  • sync.WaitGroup is used to wait for all goroutines to complete.
  • wg.Add(1) increments the WaitGroup counter for each goroutine.
  • defer wg.Done() decrements the counter when the goroutine completes.
  • wg.Wait() blocks the main function until the counter is zero, ensuring all goroutines have finished.

Conclusion

Goroutines are a powerful feature in Go that enable concurrent programming with minimal overhead. By understanding how to create and manage goroutines, you can build efficient and responsive applications. In the next section, we will explore channels, which are essential for communication and synchronization between goroutines.

© Copyright 2024. All rights reserved