Contents
Concurrency in Go
Syntax
Goroutines are lightweight threads managed by the Go runtime. They allow you to run functions concurrently, making Go highly efficient for parallel processing tasks. Goroutines are cheaper in terms of memory and processing resources compared to traditional threads. They are created using the go
keyword before a function call. The Go runtime schedules and manages the execution of goroutines, enabling high concurrency with minimal overhead.
Example:
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
func main() {
go printNumbers() // This runs concurrently
fmt.Println("Goroutine started")
// Wait for a while to allow goroutine to complete
time.Sleep(time.Second)
}
Channels
Channels are a way to communicate between goroutines. They provide a mechanism for one goroutine to send data to another goroutine in a thread-safe manner. Channels can be used to synchronize tasks, coordinate actions, and pass messages. You can create a channel with a specific type, and then use the <-
operator to send and receive data. Channels are a core part of Go’s concurrency model.
Example:
func sum(a, b int, resultChan chan int) {
resultChan <- a + b // Send result to channel
}
func main() {
resultChan := make(chan int)
go sum(2, 3, resultChan)
result := <-resultChan // Receive result from channel
fmt.Println(result) // Output: 5
}
Buffered Channels
Buffered channels are channels with a capacity to hold a fixed number of values before any goroutine is blocked on sending or receiving data. They allow for asynchronous communication, meaning a goroutine can send data to a channel without blocking if the buffer is not full. Buffered channels are useful when you want to decouple the timing of sending and receiving operations between goroutines.
Example:
func main() {
ch := make(chan int, 2) // Buffered channel with capacity 2
ch <- 1 // Does not block
ch <- 2 // Does not block
fmt.Println(<-ch) // Output: 1
fmt.Println(<-ch) // Output: 2
}
Select Statement
The select
statement in Go is used to handle multiple channel operations. It allows a goroutine to wait on multiple channels and proceed with the first channel that is ready to send or receive data. The select
statement is similar to a switch
but for channels, and it helps in managing complex communication patterns between goroutines. It ensures that the program does not block on a single channel when there are multiple channels to monitor.
Example:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Channel 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Channel 2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
}
}
Sync Package
The sync
package in Go provides basic synchronization primitives such as mutexes, wait groups, and once initialization. These tools help manage concurrency and ensure safe access to shared data across goroutines. The sync.Mutex
type is used to lock critical sections of code to prevent race conditions, while sync.WaitGroup
is used to wait for a collection of goroutines to finish executing. sync.Once
ensures that a piece of code runs only once, regardless of how many times it is called.
Example (Mutex and WaitGroup):
var (
counter int
mutex sync.Mutex
wg sync.WaitGroup
)
func increment() {
defer wg.Done()
mutex.Lock()
counter++
mutex.Unlock()
}
func main() {
for i := 0; i < 5; i++ {
wg.Add(1)
go increment()
}
wg.Wait()
fmt.Println("Final Counter:", counter) // Output: Final Counter: 5
}