Golang by Example

Logo

Fearless Concurrency

Concurrency is a powerful feature in Go that allows multiple tasks to run simultaneously. Go's concurrency model is based on goroutines and channels, which make it easy to write concurrent programs without the complexity of traditional threading models.

Goroutines

Goroutines are lightweight threads managed by the Go runtime. They are created using the go keyword followed by a function call. When a goroutine is created, it runs concurrently with the rest of the program. Goroutines are very efficient and can be created in large numbers without significant overhead.

The execution of the program above will be as follows:

Channel

Channels are a way for goroutines to communicate with each other. They provide a safe way to send and receive data between goroutines. Channels are created using the make function, and they can be used to send and receive values of a specific type. It is important to note that channels are blocking by default, meaning that if a goroutine tries to send a value on a channel and there is no other goroutine ready to receive it, the sending goroutine will block until the value is received.

The order of the output may vary each time you run the program, as the goroutines are scheduled by the Go runtime. The program will print four messages, each sent from a different goroutine to the main goroutine through the channel.

Buffered Channels

Buffered channels are a type of channel that allows you to send multiple values without blocking the sending goroutine until the buffer is full. When a buffered channel is created, you specify its capacity, which determines how many values it can hold before blocking.

Closing Channels

You may need to close a channel when you are done sending values to it. Closing a channel signals to the receiving goroutine that no more values will be sent on that channel. This is important for preventing deadlocks and ensuring that the receiving goroutine can exit gracefully.

To close a channel, you use the close function. Once a channel is closed, any attempts to send values on it will result in a panic. However, you can still receive values from a closed channel until it is empty.

The program above demonstrates how to close a channel after sending all values. The producer function sends three messages to the channel and then closes it. The main function receives messages from the channel using a for range loop, which automatically stops when the channel is closed. After the channel is closed, any attempts to receive from it will return the zero value of the channel's type and a boolean indicating whether the channel is still open.

Unlike files or network connections, channels don't typically require explicit closing. You should only close a channel when you need to indicate to receivers that no more data will be sent. In many scenarios, receiving goroutines can simply complete their work without the channel being formally closed, as the program can proceed normally through other synchronization mechanisms.

Range and Close

The for range loop can be used to receive values from a channel until it is closed. This is a convenient way to process all values sent on a channel without needing to check for closure manually.

Select statement

Go's select statement is one of key concurrency feature that enables monitoring multiple channel operations simultaneously. Unlike the switch statement which evaluates expressions, select blocks until one of its cases can proceed. This mechanism is particularly valuable for managing multiple channels concurrently and implementing timeout functionality in your programs.

select allows you to wait on multiple channel operations. In the example above, the program waits for either ch1 or ch2 to receive a value. If neither channel receives a value within 3 seconds, the program prints "Timeout". This is useful for implementing timeouts in concurrent operations.

There is default case in the second select statement, which executes if neither channel is ready. This allows you to handle cases where a channel may not be ready for communication.

Wait Groups

When working with multiple goroutines, you often need to wait for all of them to finish before proceeding. The sync package provides a WaitGroup type that allows you to wait for a collection of goroutines to finish executing. A WaitGroup is a synchronization primitive that allows you to wait for a collection of goroutines to finish executing.

You can add the number of goroutines to the WaitGroup using the Add method, and then call Done in each goroutine when it finishes. Finally, you can call Wait to block until all goroutines have completed.

Mutual Exclusion

When multiple goroutines access shared data, you need to ensure that only one goroutine can access the data at a time. This is known as mutual exclusion. Go provides the sync package, which includes the Mutex type for achieving mutual exclusion. A Mutex is a mutual exclusion lock that can be used to protect shared data from concurrent access. You can use the Lock method to acquire the lock and the Unlock method to release it.

With mutex protection, the account balance is always correct because each operation has exclusive access to the balance variable during its critical section. Now let's compare this with the example without mutex protection.

This example demonstrates why mutexes are essential when multiple goroutines access shared data. The program simulates concurrent bank transactions:

This is a classic race condition. The mutex ensures that operations on shared data happen atomically, preventing inconsistent results.

Conclusion

💡 Quiz
What is the purpose of the WaitGroup in Go?
💡 Quiz
What is the purpose of the Mutex in Go?
💡 Quiz
Why is it important to close a channel in Go?

We have covered the basics of concurrency in Go, including goroutines, channels, and synchronization mechanisms like WaitGroup and Mutex. Concurrency is a powerful feature that allows you to write efficient and responsive programs. By understanding how to use goroutines and channels effectively, you can take full advantage of Go's concurrency model. Concurrency is a powerful feature in Go that allows you to write efficient and responsive programs. By understanding how to use goroutines and channels effectively, you can take full advantage of Go's concurrency model.


Table of contents

Hello World - Begin with the classic Hello World program
Primitives - Learn about the basic data types in Go
Flow Control - Controlling the flow of your program
Struct - All about struct
Functions - Define and call functions
Methods and Interfaces - Methods and interfaces in Go
Error Handling - Handling errors idiomatically
Concurrency - Goroutines and channels
Anti Patterns - Anti patterns in Go
Libraries - Standard library and third-party libraries
Testing - Writing unit tests with `go test`
Benchmarking - Performance benchmarks
Containerization - Dockerize your Go app
What's Next? - Explore the next steps in your Golang journey


Share to: