Introduction
I have been hearing a lot lately about concurrency and precisely how concurrency manifests in its uniqueness, elegance, and simplicity when it comes to Golang, although I only knew the very basics of Go, I've decided to take a shot to learn concurrency patterns that Go provides.
One of the main characteristics of Go (besides its great mascot) is concurrency, which is intended to make it simple to develop concurrent applications that can effectively leverage many CPUs and manage a lot of concurrent requests or connections.
These are some crucial concepts and tools for concurrency in Go:
Goroutines
Goroutines: Goroutines are lightweight threads that are managed by the Go runtime, and they are used to execute concurrent tasks. Goroutines are cheap to create and have a small memory footprint, so you can use them liberally to parallelize tasks and handle many concurrent requests or connections. You can start a new goroutine using the go keyword before a function call:
func main() {
go myFunc() // Start a new goroutine to execute myFunc concurrently
// ...
}
func myFunc() {
// ...
}
Channels
Channels: Channels are used for communication and synchronization between goroutines. Channels provide a way to pass data between goroutines and coordinate their execution. Channels can be used to implement various concurrency patterns, such as producer-consumer. You can create a channel using the make function:
func main() {
// create a channel with make function
ch := make(chan int)
go func() {
// some code to send data through channel
ch <- 42
}()
data := <-ch
// use data received from channel
}
WaitGroup
WaitGroup: WaitGroup is used to wait for a group of goroutines to complete before continuing. WaitGroup provides a way to synchronize the execution of multiple goroutines and ensure that all of them have completed their tasks before proceeding. You can create a WaitGroup using the sync
package:
var wg sync.WaitGroup // Create a WaitGroup variable
func main() {
for i := 0; i < 5; i++ {
wg.Add(1) // Increment the WaitGroup counter for each goroutine
go myFunc(&wg)
}
wg.Wait() // Wait for all goroutines to complete
}
func myFunc(wg *sync.WaitGroup) {
defer wg.Done() // Decrement the WaitGroup counter when the goroutine completes
// ...
}
Select statement
Select statement: The select statement is used to wait for multiple channels to receive or send data, and it allows you to choose the first channel that is ready. The select statement can be used to implement non-blocking communication and timeouts. Here is an example of using the select statement to receive data from multiple channels:
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go produce(ch1)
go produce(ch2)
for i := 0; i < 2; i++ {
select {
case x := <-ch1:
fmt.Println("Received from ch1:", x)
case x := <-ch2:
fmt.Println("Received from ch2:", x)
}
}
}
func produce(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
Mutex
Mutex: Mutex is a mutual exclusion lock used to protect shared resources from concurrent access. Mutex ensures that only one goroutine can access a shared resource at a time, preventing race conditions and data races. You can create a Mutex using the sync package:
var mu sync.Mutex // Create a Mutex variable
func myFunc() {
mu.Lock() // Acquire the lock before accessing the shared resource
// ...
mu.Unlock() // Release the lock after accessing the shared resource
}
Atomic
Atomicity pattern, Atomic: To ensure Atomicity and Atomic operations, The atomic package provides low-level atomic operations for managing shared memory. Atomic operations can be used to perform read-modify-write operations on shared variables, without the risk of race conditions.
type Counter struct {
value int32 // must use int32 for atomic operations
}
func (c *Counter) Increment() {
atomic.AddInt32(&c.value, 1) // increment value atomically
}
func (c *Counter) Value() int32 {
return atomic.LoadInt32(&c.value) // read value atomically
}
func main() {
var wg sync.WaitGroup
var counter Counter
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println(counter.Value())
}
These are some of the key tools and concepts in Go for concurrency.
Conclusion
Golang provides a rich set of standard library functions and packages for concurrency. With these tools, you can write efficient, scalable concurrent-prone programs in Go.