Buffered Channels in Go
In this tutorial, we are going to discuss buffered channels in Go language. All the channels we discussed in the previous tutorial were unbuffered. As discussed in the channels tutorial, sends and receives to an unbuffered channel are blocking.
Channels can be defined as pipes using which Goroutines communicate. Like water flows from one end to another in a pipe, data can be sent from one end and received from other using channels.
By default, channels are unbuffered, which states that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) that are ready to receive the sent value.
Buffered channels allow accepting a limited number of values without a corresponding receiver for those values. It is possible to create a channel with a buffer.
It is possible to create a channel with a buffer. Buffered channels are blocked only when the buffer is full. Similarly, receiving from a buffered channel are blocked only when the buffer is empty.
We can create Buffered channels by passing an additional capacity parameter to the make function, which specifies the size of the buffer.
ch := make(chan type, capacity)
Here, capacity in the above syntax should be greater than 0 for a channel to have a buffer. The capacity for an unbuffered channel is 0 by default, and hence it omits the capacity parameter.
Let us write some code and create a buffered channel.
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 5)
ch <- "Ashok Kumar"
ch <- "Sai"
ch <- "Rama"
ch <- "Seetha"
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Output
Ashok Kumar
Sai
Rama
Seetha
In the above example, we create a buffered channel with a capacity of 5. Since the channel has a capacity of 5, it is possible to write 5 strings into the channel without being blocked.
We write 4 strings to the channel, and the channel does not block. We read the 4 strings, respectively.
Another Example
Let us look at one more example of the buffered channel in which the values to the channel are written in a concurrent Goroutine and read from the main Goroutine.
This example will help us better understand when writes to a buffered channel block.
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("Successfully wrote", i, "to channel")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(1 * time.Second)
for v := range ch {
fmt.Println("read value", v, "from channel")
time.Sleep(1 * time.Second)
}
}
Output
Successfully wrote 0 to channel
Successfully wrote 1 to channel
read value 0 from channel
Successfully wrote 2 to channel
read value 1 from channel
Successfully wrote 3 to channel
read value 2 from channel
Successfully wrote 4 to channel
read value 3 from channel
read value 4 from channel
In the above example, a buffered channel ch of capacity 2 is created in the main Goroutine and passed to the write Goroutine. Then the main Goroutine sleeps for 1 second.
During this time, the write Goroutine is running concurrently. The write Goroutine has a for loop, which writes numbers from 0 to 4 to the ch channel.
The capacity of this buffered channel is 2. The write Goroutine will be able to immediately write values 0 and 1 to the ch channel, and then it blocks until at least one value is read from the ch channel.
So this program will print the following 2 lines immediately.
Successfully wrote 0 to channel
Successfully wrote 1 to channel
After printing the above two lines, the writes to the ch channel in the write Goroutine are blocked until someone reads from the ch channel.
Since the main Goroutine sleeps for 1 second before reading from the channel, the program will not print anything for the next 1 second.
The main Goroutine wakes up after 1 second and starts reading from the ch channel using a for range loop, prints the read value, and then sleeps for 1 second again, and this cycle continues until the ch is closed. So the program will print the following lines after 1 second,
read value 0 from channel
Successfully wrote 2 to channel
This will continue until all values are written to the channel and closed in the write Goroutine. The final output is shown in the output section.
Deadlock
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "Ashok Kumar"
ch <- "Sai"
ch <- "Rama"
ch <- "Seetha"
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Output
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox206514869/prog.go:12 +0xb9
In the above example, we write 4 strings to a buffered channel of capacity 3. When the control reaches the fourth write, the write is blocked since the channel has exceeded its capacity.
Now some Goroutine must read from the channel for the write to proceed, but in this case, there is no concurrent routine reading from this channel. Hence there will be a deadlock, and the program will panic at run time.
That’s all about the Buffered Channels in Go language. If you have any queries or feedback, please write us email at contact@waytoeasylearn.com. Enjoy learning, Enjoy Go language.!!