Golang Tutorial
Fundamentals
Control Statements
Functions & Methods
Structure
Arrays & Slices
String
Pointers
Interfaces
Concurrency
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.
Definition: A goroutine is a lightweight thread of execution managed by the Go runtime. It's a function that runs concurrently with other functions.
Cost: Goroutines are very cheap. You can create thousands or even millions of goroutines in a single program without exhausting system resources.
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.
Creation:
go functionName()
By adding the go
keyword before a function call, it will be executed in a new goroutine.
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.
Use Case: Whenever you have tasks that can run concurrently (like handling multiple client requests or performing many calculations), use goroutines.
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.
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.
Size: A thread often has a larger stack size, typically in the range of megabytes.
Creation: In many languages, threads are created using system or library calls, and the way you do this varies by language and platform.
Scheduling: Threads are preemptively scheduled by the OS kernel.
Use Case: Threads are suitable for tasks that require significant computation and need to run in parallel on multi-core processors.
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.
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.
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.
Memory: Goroutines use less memory than threads, making it feasible to have hundreds of thousands of concurrent goroutines.
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.
Concurrency in Golang: Goroutines and Threads:
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) } }
Advantages of Goroutines Over Threads in Golang:
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() }
Concurrency Models in Golang: Goroutines and Threads:
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) } }
Thread Safety vs Goroutine Safety in Golang:
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) }
Golang Goroutines vs OS Threads Efficiency:
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() }
When to Use Goroutines and When to Use Threads in Golang:
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() }
Concurrency Patterns with Goroutines and Threads in Golang:
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 } }