Golang has good concurrency support using goroutines. Goroutines are lightweight threads managed by the Go runtime. Goroutines are multiplexed to fewer OS threads.
Goroutines are non-blocking, and they are not scheduled by the OS. The Go runtime schedules the goroutines.
The scheduler uses a technique called M:N scheduling. M goroutines are multiplexed to N OS threads. The scheduler schedules the goroutines to the OS threads. The scheduler uses a technique called work-stealing to schedule the goroutines
There are entities that can block the execution of goroutines. Let’s see the entities that can block the execution of goroutines.
Channel
Channels are used to communicate between goroutines. Channels can block the execution of goroutines.
When a goroutine sends data to a channel, it blocks until another goroutine receives the data from the channel.
package main
import (
"fmt"
"time"
)
func main() {
// Create a channel
ch := make(chan int)
// Launch a goroutine to send data to the channel
go func() {
fmt.Println("Sending data to the channel")
ch <- 42
fmt.Println("Data sent to the channel")
}()
// Simulate some work
time.Sleep(1 * time.Second)
// Receive data from the channel
fmt.Println("Receiving data from the channel")
data := <-ch
fmt.Println("Data received from the channel:", data)
}
Here is a table to summarize channel’s behavior based on operations:
Channel state | Operation | Behavior |
---|---|---|
empty | send | blocks |
empty | receive | blocks |
full | send | blocks |
full | receive | unblocks |
closed | send | panics |
closed | receive | returns zero value |
WaitGroup
WaitGroup is used to wait for a collection of goroutines to finish. WaitGroup can block the execution of goroutines.
When a goroutine calls WaitGroup.Wait()
, it blocks until all goroutines call WaitGroup.Done()
.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// Create a WaitGroup
var wg sync.WaitGroup
// Launch multiple goroutines
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("Goroutine", i, "started")
time.Sleep(1 * time.Second)
fmt.Println("Goroutine", i, "finished")
}(i)
}
// Wait for all goroutines to finish
wg.Wait()
fmt.Println("All goroutines finished")
}
Mutex
Mutex is a synchronization primitive that can block the execution of goroutines. Mutex is used to synchronize access to shared resources.
When a goroutine acquires a mutex, it blocks other goroutines from acquiring the mutex. The goroutine that acquires the mutex can access the shared resource.
package main
import (
"fmt"
"sync"
"time"
)
// Shared counter variable
var counter int = 0
// Mutex to protect the counter
var counterMutex sync.Mutex
func main() {
const numGoroutines = 10
// A WaitGroup to track goroutines
var wg sync.WaitGroup
// Launch multiple goroutines
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go incrementCounter(&wg)
}
// Wait for all goroutines to complete
wg.Wait()
fmt.Println("Final counter value:", counter)
}
// Safely increments the counter
func incrementCounter(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 100; i++ {
counterMutex.Lock()
counter++
counterMutex.Unlock()
time.Sleep(10 * time.Millisecond) // Simulate some work
}
}
Here’s a table to summarize mutex’s behavior based on operations:
Mutex state | Operation | Behavior |
---|---|---|
unlocked | lock | Acquires the lock |
unlocked | unlock | panics |
locked | lock | blocks |
locked | unlock | Unlocks the mutex |