Buffered and Unbuffered Channel in Golang
Overview
Goroutines made it cheaper to run several subprograms simultaneously, which was tremendously advantageous for Go developers. The good news is that Golang offers channels to make this communication possible between these peer routines. Go's channel mechanism is already incredibly potent, and understanding the fundamentals might make it even better. Whether the channel is buffered or unbuffered will affect how the application behaves and performs. You will learn more about the various Go channel types from this post.
What are Channels?
Channels are a type-safe` message that helps to communicate between goroutines. A channel connects two goroutines and synchronizes the exchange of any information that passes through it.
Developers can send data at one end of a channel while collecting data at the other, similar to how UNIX pipes work. Additionally, they offer to buffer with adjustable buffer sizes. On the other hand, pipes convey data as a stream of bytes and handle it generally. Programmers must identify the type of data and format it accordingly.
What are the Types of Channels Present in Golang?
In Golang, there are two types of channels:
- Buffered Channel
- Unbuffered Channel
Let's learn about them in a detail.
Buffered Channel and Its Properties
To conduct asynchronous communication, buffered channels are required. Before receiving any data, it could store one or more of them. We often don't require the goroutines for this type of channel to process send and receive operations simultaneously. Along with a few other conditions, received will only be blocked if there are no values in the channel to receive, and send will only be blocked if there is no buffer available to place the value being sent.
Syntax:
Code Implementation
Output
Explanation of the code:
- In the above program, we are using the ss variable to create a buffered channel that has a capacity of 2, which means it is allowed to write 2 strings without being blocked.
- The two strings in our code i.e Scaler and Golang channels are written without being blocked and print the same as an output for us.
Another Example:
Output:
Explanation of the code:
- In the above code, we have created a channel which is a buffered channel that contains capacity initially 2.
- In this channel, once the buffer is full the goroutine will go into a waiting state. And another goroutine resumes and pushes the values through the dequeued goroutine.
Deadlock in Buffered Channel
A deadlock in a buffered channel in Go occurs when a goroutine is trying to send data to a channel that is already full, or when a goroutine is trying to receive data from an empty channel.
When a goroutine tries to send data to a full channel, it blocks until there is space available in the channel buffer. If another goroutine is trying to receive data from the same channel and is also blocked, a deadlock occurs.
Similarly, when a goroutine tries to receive data from an empty channel, it blocks until there is data available in the channel buffer. If another goroutine is trying to send data to the same channel and is also blocked, a deadlock occurs.
For Example:
Output
Explanation of the code:
- In the above code, we have added one more string which is not allowed as we learned that we can not exceed our range more than the specified capacity.
- And this fall into the case of deadlock and the program will panic at run time
To prevent deadlocks in buffered channels, it's important to ensure that the number of goroutines sending data to the channel is equal to the number of goroutines receiving data from the channel.
We can use the select statement to handle multiple channels and ensure that at least one channel is unblocked to prevent the situation of no channel being ready to receive or send data. We will see this in the further section.
Unbuffered Channel and Its Properties
In Go, an unbuffered channel is a channel that can be used for both sending and receiving but doesn't have a buffer to store data. This means that when a goroutine sends data to an unbuffered channel, the data is not stored in a buffer, but is instead immediately passed to the goroutine that is trying to receive data from the channel. Similarly, when a goroutine receives data from an unbuffered channel, it blocks until data is available to be received.
Some properties of unbuffered Channels in golang:
-
No Buffer: As unbuffered channels don't have a buffer to store data capacity. This means that when a goroutine sends data to an unbuffered channel, the data is not stored in a buffer, but is instead immediately passed to the goroutine that is trying to receive data from the channel.
-
Synchronous: Unbuffered channels are synchronous, which means that a goroutine sending data to one will block until another goroutine is prepared to accept it. A goroutine that accepts data from an unbuffered channel will similarly block until new data becomes available.
Syntax:
Code Implementation
Output:
Explanation of the code:
- In the above code, we have created a channel with an empty list of receiver and sender along with the unbuffered channel which has 0 or no capacity at all(make the second argument empty).
- Next, we have created a WaitGroup which is used to hold the application for all the goroutines to complete their execution first.
- With each goroutine it will increment the counter till 2 as we have added wg. Add(2)
- The sender which is the first goroutine sends the message which is then enqueued and put into a waiting state. And the second goroutine will receive the information via the channel, which dequeues the message.
- The channel internally uses memmove() which is used to copy values from the sender side to the variable rmsg.
Another simpler example:
Output:
Selecting from Buffered vs Unbuffered Channel in Golang
In Go, multiple channels are handled with the select statement. A goroutine can use this feature to wait on many channels and act when one of them is ready to send or receive data. Select can be used with both the channels
Some of the functions of the select statement:
- It helps switch on channels and check which one can proceed immediately.
- If there are multiple channels available which are not blocking then it will select the random ones.
- If all channels are blocked, it will wait for any one of the operations else it will proceed with the default case if it's mentioned in the code.
Select with Unbuffered Channel
Output:
Explanation of the code:
In the above program, With an unbuffered channel no receive operation appears before select, so the case messages <- "hello" is not ready there is no receiver, and the default is always executed.
Buffered With Select statement
When using a buffered channel with the select statement, the goroutine will proceed with the case statement when the channel has space to send data or data to receive.
Output:
Explanation of the code:
With a buffered channel, the send operation does not block even if there's no receiver on the other end, so case messages <- "hello" is ready and runs the prints hello as an output.
Which One is Better?
- It depends on the requirement of the program or application, if we want or know how many goroutines we need to launch prior then we should go with the buffered channel as it helps us to limit the capacity and limit the amount of work.
- Unbuffered channels perform somewhat better because the send and receive processes are coordinated and when we don't know how many goroutines we need to launch.
- Also, An unbuffered channel ensures that an exchange between two goroutines occurs at the same time as the send and receive. A buffered channel provides no such assurance.
Conclusion
- A channel is created by the make function, which specifies the chan keyword and a channel's element type.
- By default channels are unbuffered in Golang.
- A channel with a single "arrow" pointing towards it (e.g. <-chan int) is a receive-only channel, and can only be used to receive values. A channel with a single "arrow" pointing away from it (e.g. chan<- int) is a send-only channel, and can only be used to send values. A channel with an arrow pointing in both directions (e.g. chan int) can be used to both send and receive values.
- Unbuffered channel is always 0 by default and hence ignored during channel definition, the value of capacity larger than zero indicates that it is a buffered channel.
- Buffered channels in Golang are used to communicate concurrently executing functions by sending and receiving information from a certain element type for a given capacity.
- Unbuffered Channel in Golang is used without any capacity or 0 capacity and communication succeeds only when both a sender and receiver are ready.