Golang Tutorial

Fundamentals

Control Statements

Functions & Methods

Structure

Arrays & Slices

String

Pointers

Interfaces

Concurrency

Goroutine vs Thread in Golang

Understanding the distinction between goroutines and threads is fundamental when working with Go (or Golang), as it profoundly impacts how concurrency is handled. This tutorial will shed light on the differences and similarities between goroutines and threads.

Goroutines:

  1. Definition: A goroutine is a lightweight thread of execution managed by the Go runtime. It's a function that runs concurrently with other functions.

  2. Cost: Goroutines are very cheap. You can create thousands or even millions of goroutines in a single program without exhausting system resources.

  3. Size: The initial size of a goroutine's stack is quite small, often just a few kilobytes. The stack can grow and shrink as needed.

  4. Creation:

go functionName()

By adding the go keyword before a function call, it will be executed in a new goroutine.

  1. Scheduling: Goroutines are cooperatively scheduled by the Go runtime. The Go runtime has its own scheduler that uses a technique known as "m:n threading" to map many goroutines onto a smaller number of OS threads.

  2. Use Case: Whenever you have tasks that can run concurrently (like handling multiple client requests or performing many calculations), use goroutines.

Threads:

  1. Definition: A thread is the smallest unit of execution that can be scheduled by the OS. It's part of a process, and multiple threads within a process share the same memory space.

  2. Cost: Threads are more expensive than goroutines in terms of memory and context-switching overhead. The exact cost varies by OS, but generally, a program can't create as many threads as goroutines without running into performance or resource issues.

  3. Size: A thread often has a larger stack size, typically in the range of megabytes.

  4. Creation: In many languages, threads are created using system or library calls, and the way you do this varies by language and platform.

  5. Scheduling: Threads are preemptively scheduled by the OS kernel.

  6. Use Case: Threads are suitable for tasks that require significant computation and need to run in parallel on multi-core processors.

Comparison:

  1. Management: Goroutines are managed by Go, whereas threads are managed by the OS. This means the Go runtime can make intelligent decisions about how to handle goroutines without involving the OS, leading to more efficient context switching.

  2. Concurrency Model: Go's concurrency model is based on the idea of "Do not communicate by sharing memory; instead, share memory by communicating." This principle is realized using channels in Go, which provide a way for goroutines to communicate safely.

  3. Efficiency: Due to their lightweight nature, goroutines are often more efficient than threads for tasks that involve I/O operations or that can be broken down into smaller, concurrent tasks.

  4. Memory: Goroutines use less memory than threads, making it feasible to have hundreds of thousands of concurrent goroutines.

Conclusion:

While threads are a more general-purpose tool for parallelism managed by the OS, goroutines are a higher-level construct provided by the Go language, optimized for the kind of scalable, networked software that Go is designed for. When writing Go code, you'll typically use goroutines and channels to achieve concurrency, letting the Go runtime handle the details of how this maps onto system threads.

  1. Concurrency in Golang: Goroutines and Threads:

    • Concurrency in Golang is achieved through Goroutines and not traditional threads.
    • Example:
      package main
      
      import (
          "fmt"
          "time"
      )
      
      func printNumbers() {
          for i := 1; i <= 5; i++ {
              time.Sleep(100 * time.Millisecond)
              fmt.Println(i)
          }
      }
      
      func main() {
          // Goroutine
          go printNumbers()
      
          // Main function
          for i := 1; i <= 5; i++ {
              time.Sleep(200 * time.Millisecond)
              fmt.Println("Main:", i)
          }
      }
      
  2. Advantages of Goroutines Over Threads in Golang:

    • Goroutines are more lightweight, have lower overhead, and are easier to manage than traditional threads.
    • Example:
      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func printNumbers(wg *sync.WaitGroup) {
          defer wg.Done()
          for i := 1; i <= 5; i++ {
              fmt.Println(i)
          }
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Goroutine
          wg.Add(1)
          go printNumbers(&wg)
      
          // Main function
          for i := 1; i <= 5; i++ {
              fmt.Println("Main:", i)
          }
      
          // Wait for Goroutine to finish
          wg.Wait()
      }
      
  3. Concurrency Models in Golang: Goroutines and Threads:

    • Golang primarily uses Goroutines and a scheduler for concurrency, as opposed to managing threads directly.
    • Example:
      package main
      
      import (
          "fmt"
          "time"
      )
      
      func printNumbers() {
          for i := 1; i <= 5; i++ {
              time.Sleep(100 * time.Millisecond)
              fmt.Println(i)
          }
      }
      
      func main() {
          // Goroutine
          go printNumbers()
      
          // Main function
          for i := 1; i <= 5; i++ {
              time.Sleep(200 * time.Millisecond)
              fmt.Println("Main:", i)
          }
      }
      
  4. Thread Safety vs Goroutine Safety in Golang:

    • Thread safety involves protecting shared data in a multi-threaded environment, while Goroutine safety is managed through Goroutine communication and channels.
    • Example:
      package main
      
      import (
          "fmt"
          "sync"
      )
      
      var sharedData int
      var mutex sync.Mutex
      
      func increment(wg *sync.WaitGroup) {
          defer wg.Done()
          mutex.Lock()
          defer mutex.Unlock()
          sharedData++
      }
      
      func main() {
          var wg sync.WaitGroup
      
          for i := 0; i < 1000; i++ {
              wg.Add(1)
              go increment(&wg)
          }
      
          // Wait for Goroutines to finish
          wg.Wait()
          fmt.Println("Result:", sharedData)
      }
      
  5. Golang Goroutines vs OS Threads Efficiency:

    • Goroutines are more efficient due to their lightweight nature and being managed by the Go scheduler, which multiplexes Goroutines onto a smaller number of OS threads.
    • Example:
      package main
      
      import (
          "fmt"
          "runtime"
          "sync"
      )
      
      func printNumbers(wg *sync.WaitGroup) {
          defer wg.Done()
          for i := 1; i <= 5; i++ {
              fmt.Println(i)
          }
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Set number of OS threads
          runtime.GOMAXPROCS(2)
      
          // Goroutine
          wg.Add(1)
          go printNumbers(&wg)
      
          // Main function
          for i := 1; i <= 5; i++ {
              fmt.Println("Main:", i)
          }
      
          // Wait for Goroutine to finish
          wg.Wait()
      }
      
  6. When to Use Goroutines and When to Use Threads in Golang:

    • Use Goroutines for concurrency in Golang due to their efficiency and ease of use. Directly managing threads is uncommon in Golang.
    • Example:
      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func printNumbers(wg *sync.WaitGroup) {
          defer wg.Done()
          for i := 1; i <= 5; i++ {
              fmt.Println(i)
          }
      }
      
      func main() {
          var wg sync.WaitGroup
      
          // Goroutine
          wg.Add(1)
          go printNumbers(&wg)
      
          // Main function
          for i := 1; i <= 5; i++ {
              fmt.Println("Main:", i)
          }
      
          // Wait for Goroutine to finish
          wg.Wait()
      }
      
  7. Concurrency Patterns with Goroutines and Threads in Golang:

    • Common concurrency patterns include fan-out, fan-in, worker pools, and pipelines using Goroutines and channels.
    • Example:
      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func main() {
          var wg sync.WaitGroup
          jobs := make(chan int, 10)
          results := make(chan int, 10)
      
          // Fan-out pattern
          for w := 1; w <= 3; w++ {
              wg.Add(1)
              go worker(w, jobs, results, &wg)
          }
      
          // Fan-in pattern
          go func() {
              wg.Wait()
              close(results)
          }()
      
          // Producer
          go func() {
              defer close(jobs)
              for i := 1; i <= 5; i++ {
                  jobs <- i
              }
          }()
      
          // Main function
          for result := range results {
              fmt.Println("Result:", result)
          }
      }
      
      func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
          defer wg.Done()
          for job := range jobs {
              // Process job and send result
              results <- job * 2
          }
      }