-
Notifications
You must be signed in to change notification settings - Fork 3
Goroutines
Johnny Boursiquot edited this page May 31, 2024
·
3 revisions
- Lightweight threads managed by the Go runtime.
- Multiplexed by the runtime over a smaller number of OS threads, making them much cheaper (memory, performance) compared to traditional threads.
- Dynamic stack growth (they start small at only a few kilobyes).
- Enable parallelism by being run across CPU cores (scheduler manages this).
- Efficient communication through channels for safe data exchange without explicit locks.
- Require synchronization.
Concurrency is about dealing with lots of things at once (e.g., multiple tasks making progress). Parallelism is about doing lots of things at once (e.g., multiple tasks running simultaneously on different CPU cores).
The first iteration of this example invokes sum
within the main
goroutine.
package main
import (
"fmt"
)
// sum calculates and prints the sum of numbers
func sum(nums []int) {
sum := 0
for _, v := range nums {
sum += v
}
fmt.Println("Result:", sum)
}
func main() {
nums := []int{1, 2, 3, 4, 5}
sum(nums)
// invoke the sum function as a goroutine
//
}
This next iteration invokes sum
in its own goroutine.
package main
import (
"fmt"
"time"
)
// sum calculates and prints the sum of numbers
func sum(nums []int) {
sum := 0
for _, v := range nums {
sum += v
}
fmt.Println("Result:", sum)
}
func main() {
nums := []int{1, 2, 3, 4, 5}
// sum(nums)
// invoke the sum function as a goroutine
go sum(nums)
// force main thread to sleep
time.Sleep(100 * time.Millisecond)
}
Discussion:
- Why do we need
time.Sleep
? - What would happen without the sleep?
Go's standard library includes package sync
which provides basic synchronization primitives, including mutual exclusion locks, as well as handy features such as the WaitGroup
which helps us wait for a collection of goroutines to finish their work.
package main
import (
"fmt"
"sync"
)
func main() {
// given a list of names
names := []string{"James", "Toni", "Maya"}
// initialize a map to store the length of each name
namesMap := make(map[string]int, len(names))
// initialize a wait group for the goroutines that will be launched
var wg sync.WaitGroup
wg.Add(len(names))
var mu sync.Mutex
// launch a goroutine for each name we want to process
for _, name := range names {
go func(name string) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
namesMap[name] = len(name)
}(name)
}
// wait for all goroutines to finish
wg.Wait()
// print the map
fmt.Println(namesMap)
}
- Mutexes are ideal for protecting access to shared data structures accessed by multiple goroutines without the need for communication.
- Mutexes are simpler when dealing with straightforward critical sections.
- What would happen without the locks?
- How do I catch possible data races in my programs during development?
See exercise 1.