Golang (Go) is renowned for its straightforward and effective approach to concurrent programming. One of its guiding philosophies is the phrase: “Do not communicate by sharing memory; instead, share memory by communicating.” To truly understand the essence of this philosophy, let’s explore it using Golang examples that illustrate it.
Traditional Approach in Go: Mutex Locks and Shared Memory
Even though Go provides modern concurrency primitives like channels, it still allows for the use of mutex locks for those who prefer a more traditional approach to concurrency. Below the diagram of the sample code that we present right after it.
Example: Summing Numbers Concurrently
Let’s consider a simple task: summing an array of numbers. We can divide this task among multiple Goroutines and have each one sum part of the array. We could use a shared variable to accumulate the total sum, making sure to lock it before each update.
package main
import (
"fmt"
"sync"
)
func main() {
var sum int
var mu sync.Mutex
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var wg sync.WaitGroup
wg.Add(2)
go func() {
for _, n := range numbers[:5] {
mu.Lock()
sum += n
mu.Unlock()
}
wg.Done()
}()
go func() {
for _, n := range numbers[5:] {
mu.Lock()
sum += n
mu.Unlock()
}
wg.Done()
}()
wg.Wait()
fmt.Println("Total Sum:", sum)
}
Issues:
- Complexity: We need to remember to lock and unlock the mutex to ensure exclusive access to the
sum
variable. - Potential for Errors: If you forget to lock or unlock correctly, you’ll run into race conditions.
- Reduced Readability: The core logic of summing numbers is interspersed with mutex operations, making the code less straightforward.