Golang Tutorial

Fundamentals

Control Statements

Functions & Methods

Structure

Arrays & Slices

String

Pointers

Interfaces

Concurrency

Multiple Goroutines in Golang

In Go, concurrency is achieved using goroutines, which are lightweight threads managed by the Go runtime. They provide an easy way to run functions concurrently, making it simpler to design and implement parallel applications.

Understanding Goroutines

Let's dive deep into goroutines and see them in action.

1. Creating a Goroutine

You can launch a goroutine by simply using the go keyword followed by a function invocation.

Example:

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world") // starts a new goroutine
    say("hello")    // main goroutine
}

In the above example, say("world") runs concurrently with say("hello") due to the go keyword.

2. Synchronization Using Channels

While goroutines are powerful, there often arises a need to synchronize and communicate between them. This is achieved using channels in Go.

Example:

Here's a simple demonstration of using channels to synchronize two goroutines:

package main

import "fmt"

func greet(message chan string) {
    fmt.Println("Hello")
    message <- "Done"
}

func main() {
    message := make(chan string)
    
    go greet(message)
    
    // Block until we receive a notification from the `greet` goroutine
    msg := <-message
    fmt.Println(msg)
}

In this example, the main function will block until it receives a "Done" message from the greet goroutine.

3. Multiple Goroutines and Channels

You can easily expand on this to manage multiple goroutines:

package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan int) {
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
    ch <- id
}

func main() {
    ch := make(chan int)
    for i := 0; i < 5; i++ {
        go worker(i, ch)
    }

    for i := 0; i < 5; i++ {
        fmt.Printf("Main received data from worker %d\n", <-ch)
    }
}

Here, five workers are started concurrently, and the main function waits to receive data from all of them.

4. Select Statement

In scenarios where you need to handle multiple channels, the select statement provides a way to proceed with the first receivable channel:

package main

import "fmt"

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

    go func() {
        ch1 <- "from ch1"
    }()
    
    go func() {
        ch2 <- "from ch2"
    }()

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

Key Takeaways:

  1. Goroutines provide an easy mechanism for concurrent execution in Go.
  2. Channels are the way goroutines communicate safely and synchronize their execution.
  3. The select statement allows a way to handle multiple channels.

Understanding goroutines and channels is fundamental to writing efficient, concurrent programs in Go. With practice, you can leverage them to design robust scalable systems.

  1. Concurrency with multiple Goroutines in Golang:

    • Description: Golang supports concurrency through Goroutines, which are lightweight threads. Multiple Goroutines can execute concurrently, enabling efficient parallelism.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func printNumbers() {
          for i := 1; i <= 5; i++ {
              fmt.Println("Number:", i)
          }
      }
      
      func printLetters() {
          for char := 'a'; char <= 'e'; char++ {
              fmt.Println("Letter:", string(char))
          }
      }
      
      func main() {
          // Start two Goroutines concurrently
          go printNumbers()
          go printLetters()
      
          // Wait for Goroutines to finish (main function may exit without waiting)
          fmt.Println("Waiting for Goroutines to finish...")
          select {}
      }
      
  2. How to create and manage multiple Goroutines in Golang:

    • Description: You can create Goroutines using the go keyword. Managing them involves ensuring they complete their execution or waiting for their results.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func printNumbers() {
          for i := 1; i <= 5; i++ {
              fmt.Println("Number:", i)
          }
      }
      
      func printLetters() {
          for char := 'a'; char <= 'e'; char++ {
              fmt.Println("Letter:", string(char))
          }
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Increment the WaitGroup counter for each Goroutine
          wg.Add(2)
      
          // Start two Goroutines concurrently
          go func() {
              defer wg.Done()
              printNumbers()
          }()
      
          go func() {
              defer wg.Done()
              printLetters()
          }()
      
          // Wait for all Goroutines to finish
          wg.Wait()
      }
      
  3. Synchronization between multiple Goroutines in Golang:

    • Description: Synchronization ensures that Goroutines coordinate their execution to avoid conflicts or race conditions. This is achieved using synchronization primitives like sync.Mutex and channels.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      var counter int
      var mu sync.Mutex
      
      func incrementCounter() {
          mu.Lock()
          counter++
          mu.Unlock()
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Increment the WaitGroup counter for each Goroutine
          wg.Add(2)
      
          // Start two Goroutines concurrently
          go func() {
              defer wg.Done()
              for i := 0; i < 1000; i++ {
                  incrementCounter()
              }
          }()
      
          go func() {
              defer wg.Done()
              for i := 0; i < 1000; i++ {
                  incrementCounter()
              }
          }()
      
          // Wait for all Goroutines to finish
          wg.Wait()
      
          fmt.Println("Counter:", counter)
      }
      
  4. Communication between multiple Goroutines in Golang:

    • Description: Goroutines can communicate using channels, allowing them to safely exchange data.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func sendData(ch chan int, wg *sync.WaitGroup) {
          defer wg.Done()
          ch <- 42 // Send data to the channel
      }
      
      func receiveData(ch chan int, wg *sync.WaitGroup) {
          defer wg.Done()
          data := <-ch // Receive data from the channel
          fmt.Println("Received data:", data)
      }
      
      func main() {
          var wg sync.WaitGroup
          ch := make(chan int)
      
          // Increment the WaitGroup counter for each Goroutine
          wg.Add(2)
      
          // Start two Goroutines concurrently
          go sendData(ch, &wg)
          go receiveData(ch, &wg)
      
          // Wait for all Goroutines to finish
          wg.Wait()
      
          // Close the channel after communication is done
          close(ch)
      }
      
  5. Golang wait group for coordinating multiple Goroutines:

    • Description: The sync.WaitGroup is a synchronization primitive that allows the main Goroutine to wait for other Goroutines to finish.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func worker(id int, wg *sync.WaitGroup) {
          defer wg.Done()
          fmt.Println("Worker", id, "is done")
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Increment the WaitGroup counter for each Goroutine
          for i := 1; i <= 3; i++ {
              wg.Add(1)
              go worker(i, &wg)
          }
      
          // Wait for all Goroutines to finish
          wg.Wait()
      
          fmt.Println("All workers are done")
      }
      
  6. Dealing with race conditions in Golang with multiple Goroutines:

    • Description: Race conditions occur when multiple Goroutines access shared data concurrently. Golang provides tools like sync.Mutex to mitigate race conditions.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      var counter int
      var mu sync.Mutex
      
      func incrementCounter() {
          mu.Lock()
          counter++
          mu.Unlock()
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Increment the WaitGroup counter for each Goroutine
          for i := 0; i < 1000; i++ {
              wg.Add(1)
              go func() {
                  defer wg.Done()
                  incrementCounter()
              }()
          }
      
          // Wait for all Goroutines to finish
          wg.Wait()
      
          fmt.Println("Counter:", counter)
      }
      
  7. Cancelation and timeout handling in multiple Goroutines in Golang:

    • Description: You can use channels for cancelation or timeouts in Goroutines. Context package is also commonly used for graceful cancelation.

    • Code:

      package main
      
      import (
          "fmt"
          "sync"
          "time"
      )
      
      func worker(id int, cancel chan struct{}, wg *sync.WaitGroup) {
          defer wg.Done()
      
          select {
          case <-cancel:
              fmt.Println("Worker", id, "canceled")
          case <-time.After(2 * time.Second):
              fmt.Println("Worker", id, "is done")
          }
      }
      
      func main() {
          var wg sync.WaitGroup
          cancel := make(chan struct{})
      
          // Increment the WaitGroup counter for each Goroutine
          for i := 1; i <= 3; i++ {
              wg.Add(1)
              go worker(i, cancel, &wg)
          }
      
          // Cancel all Goroutines after 1 second
          time.Sleep(1 * time.Second)
          close(cancel)
      
          // Wait for all Goroutines to finish
          wg.Wait()
      
          fmt.Println("All workers are done or canceled")
      }