Advanced Golang
Concurrency
Channels Reading and Writing
In this article, you'll learn how to use channels in Go to enable communication between goroutines. We will explore how to create channels, send data to them, receive data from them, and understand their blocking behavior. This guide is ideal for developers looking to build concurrent programs using Go.
Declaring and Creating a Channel
To begin, declare a channel using Go’s built-in make
function. In the example below, we create a channel that only transmits string data, ensuring that only string values can be communicated.
package main
func main() {
ch := make(chan string)
}
// sends data to the channel
func sell() {
}
// receives data from the channel
func buy() {
}
Sending Data Through a Channel
Next, update the sell
function to send a string value (e.g., "Furniture") via the channel. Instead of using the channel ch
directly within the function (which causes unresolved reference errors), pass the channel as a parameter.
package main
func main() {
ch := make(chan string)
}
// sends data to the channel
func sell(ch chan string) {
ch <- "Furniture"
}
// receives data from the channel
func buy() {
}
Note
A channel in Go is passed by reference, similar to slices or maps. Therefore, you do not need to use the ampersand (&
) or asterisk (*
) operators.
Receiving Data from the Channel
Now, enhance the buy
function to receive a string from the channel and print it. In the updated code below, the sell
function also logs a message indicating that data was sent to the channel.
package main
import "fmt"
func main() {
ch := make(chan string)
// sends data to the channel as a goroutine
go sell(ch)
// receives data from the channel
fmt.Println(<-ch)
}
func sell(ch chan string) {
ch <- "Furniture"
fmt.Println("Sent data to the channel")
}
func buy() {
}
Blocking Behavior and Order of Execution
The buy
function demonstrates how to wait for data by using a blocking receive operation. Here, a message "Waiting for data" is printed before retrieving the value from the channel and displaying it.
package main
import "fmt"
func main() {
ch := make(chan string)
// sends data to the channel as a goroutine
go sell(ch)
// receives data from the channel
buy(ch)
}
// sends data to the channel
func sell(ch chan string) {
ch <- "Furniture"
fmt.Println("Sent data to the channel")
}
// receives data from the channel
func buy(ch chan string) {
fmt.Println("Waiting for data")
val := <-ch
fmt.Println("Received data -", val)
}
In this scenario, the sell
function sends "Furniture" to the channel and logs a confirmation message, while the buy
function waits, retrieves the data, and prints the received value. To prevent the program's main function from exiting before the goroutines complete, consider implementing a timer or using a WaitGroup, depending on your application's needs.
Below is an example of the program's output:
go run main.go
Waiting for data
Received data - Furniture
Sent data to the channel
Behavior of Unbuffered Channels
Understanding the nature of unbuffered channels is crucial when designing concurrent applications in Go. Here are a few key points:
- A value sent to a channel can be received only once.
- By default, channels are unbuffered. This means that a send operation blocks the sending goroutine until another goroutine performs a receive.
- Similarly, a receive operation blocks until data is available, ensuring synchronization between goroutines.
The example below illustrates that the send operation in sell
is blocked at the line ch <- "Furniture"
until another goroutine calls the receive operation on the same channel:
package main
func sell(ch chan string) {
ch <- "Furniture"
fmt.Println("Sent data to the channel")
}
func buy(ch chan string) {
fmt.Println("Waiting for data")
val := <-ch
fmt.Println("Received data -", val)
}
When executed, you may observe output similar to:
go run main.go
Waiting for data
Received data - Furniture
Sent data to the channel
Warning
Remember that because goroutines run concurrently, the ordering of the printed messages after unblocking may vary with each execution.
Summary
This article demonstrated how to create unbuffered channels in Go to exchange data between goroutines effectively. The key points covered include:
- Channels are created with the
make
function and are type-restricted. - Channels are passed by reference, eliminating the need for explicit pointers.
- Unbuffered channels block the sending goroutine until the data is received, ensuring synchronization.
- The order of execution between concurrent goroutines is non-deterministic once unblocked.
By mastering these concepts, you'll be better equipped to build robust, concurrent applications in Go. For further reading on Go concurrency, check out Go Concurrency Patterns and stay tuned for more advanced topics on Go programming.
Watch Video
Watch video content