Golang Tutorial
Fundamentals
Control Statements
Functions & Methods
Structure
Arrays & Slices
String
Pointers
Interfaces
Concurrency
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.
Let's dive deep into goroutines and see them in action.
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.
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.
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.
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) } } }
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.
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 {} }
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() }
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) }
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) }
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") }
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) }
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") }